模板方法模式(TEMPLATE METHOD),用于定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤,属于类行为型模式。模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。模板方法模式提供了一个模板方法来定义算法框架,而某些具体步骤的实现可以在其子类中完成。模板方法模式是一种基于继承的代码复用技术。
一、使用场景
1、一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。比如乘坐公交的步骤都是上车刷卡-乘车-下车刷卡,步骤不变,但根据目的地不同,乘车路线也不同,即乘车的行为是变化的。
2、将子类的公共行为抽取到父类中以避免代码重复。
3、控制子类扩展。模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
二、UML图
三、Java实现
package study.patterns.templatemethod;
/**
* 模板方法模式,又称好莱坞模式(回调)
* Don't call me,I'll call you
* @author qbg
*/
public class TemplateMethodPattern {
public static void main(String[] args) {
GeneralPenguin gp = new GeneralPenguin();
gp.happyDay();
System.out.println("==========================");
BeanPenguin bp = new BeanPenguin();
bp.happyDay();
}
}
abstract class Penguin{
/**
* 模板方法:用于定义南极企鹅的快乐一天
* 模板方法定义了整个算法框架,需子类完全继承,而不能覆盖。
*/
public void happyDay(){
eat();
if(isBean()){
beated();
}else{
beatBean();
}
sleep();
}
/**
* 抽象方法:吃饭
*/
public abstract void eat();
/**
* 抽象方法:睡觉
*/
public abstract void sleep();
/**
* 钩子方法:判断是否为"豆豆",默认实现返回false,即不是豆豆企鹅
* 钩子方法的引入使得子类可以控制父类的行为。
*/
public boolean isBean(){
return false;
}
/**
* 具体方法:父类对打豆豆的行为进行了限制,只能是这样打.
* 所以一般不要覆盖父类的具体方法。
*/
public void beatBean(){
System.out.println("打豆豆...追着打...");
}
/**
* 具体方法:父类限制了豆豆挨打的行为,只能这样被打
* 所以一般不要覆盖父类的具体方法。
*/
public void beated(){
System.out.println("挨打...跑...");
}
}
/**
* 普通的企鹅,一天经历:吃饭,睡觉,打豆豆
*/
class GeneralPenguin extends Penguin{
@Override
public void eat() {
System.out.println("快快乐乐的吃饭,因为吃饱要去打豆豆...");
}
@Override
public void sleep() {
System.out.println("高高兴兴的睡觉,因为睡醒要去打豆豆...");
}
}
/**
* 企鹅豆豆:一天经历:吃饭,睡觉,挨打
*/
class BeanPenguin extends Penguin{
@Override
public void eat() {
System.out.println("吃不下去,因为总挨打...");
}
@Override
public void sleep() {
System.out.println("睡不着觉,因为总挨打...");
}
/**
* 豆豆企鹅需要覆盖isBean(),从而控制父类的行为.
*/
@Override
public boolean isBean() {
return true;
}
}
运行结果:
快快乐乐的吃饭,因为吃饱要去打豆豆...
打豆豆...追着打...
高高兴兴的睡觉,因为睡醒要去打豆豆...
==========================
吃不下去,因为总挨打...
挨打...跑...
睡不着觉,因为总挨打...
四、好莱坞法则
好莱坞原则,英文“Don't call me; I'll call you”,《编程导论(JAVA)》中将其作为回调的近义词。
1、多态
图1 依赖抽象类型,针对接口编程,遵循OCP代码的基本结构。
Client的测试方法test()代码如下:
IServer s = new Server();
s.foo(5);
对上述代码的解释:
s被初始化后,将按照s实际指向的对象类型(Server), 动态绑定 foo()的代码块,这就是面向对象的多态特性。
在分层结构中,上层依赖于下层,最后依赖于基础设施(如JDK、各种框架),因而依赖必须是单向的 。如果图1中的Server是一个上层模块,那么它给出的 foo(int)代码块就称为回调 。回调与通常的非回调代码,从类图和其自身代码上,没有区别。之所以需要回调callback、隐式调用Implicit invocation (某些软件架构的作者使用的术语),是因为分层结构的一条线的存在,如图2所示。
图2 回调的应用场景
非回调代码,如果不考虑OCP,图1中的Client可以直接依赖于Server;而在图2所示的回调的应用场景中,上层的Client直接依赖于下层的Server,而下层的Server不能够依赖Client,因而不得不在下层(或基础设施/框架中)定义一个Client的父类型,如接口IClient,用于回调,采用的技术是面向对象的多态。
3、好莱坞法则
"Don't call me; I'll call you." Client(you)调用Server(me)天经地义,但是请不要轮询我,我通知你 。好莱坞原则中me是下层模块,它的通知依赖于面向对象的多态技术,通过回调接口与多态实现Server到Client的交互(Server完成某些计算或操作后通知Client自己已完成)。如果不采用通知方式(应用好莱坞原则),则上层模块可以采用轮询。
4、谁的好莱坞原则
如果站在上层模块的角度,me是上层模块,所说的好莱坞法则是对的,它反映了分层结构的依赖单向性。
如果站在下层模块的角度,me是下层模块,所说的好莱坞法则是对的,它是框架设计的基本原则。相关术语有:回调、GUI编程/事件驱动编程、依赖注入、分布式编程(RMI中也经常使用回调机制)、隐式调用、(控制反转)。
优点:
1、通过在父类中使用由抽象操作组成的模板方法来定义算法的执行流程,而具体操作由子类实现,灵活。
2、模板方法是通过继承方式实现代码复用的基本技术。
3、模板方法提供反向的控制结构,通过子类覆盖父类的钩子方法来决定是否执行某项操作。
缺点:
1、模板方法是基于继承方式实现的,所以对于可变方法较多的场景需要提供很多子类,这样会导致类的数量增加,系统变得庞大而不好维护。