模板方法
定义
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
(引用GoF 设计模式)
模板方法模型 是一种行为设计模型。 模板方法 是一个定义在父类别的方法,在 模板方法 中会呼叫多个定义在父类别的其他方法,而这些方法有可能只是抽象方法并没有实作, 模板方法 仅决定这些抽象方法的执行顺序,这些抽象方法的实作由子类别负责,并且子类别不允许覆写模板方法。
(引用维基百科)
类图
实现
1. 定义抽象类
package template;
/**
* 抽象类: 作为基类,其子类必须实现抽象方法
*/
public abstract class CaffeineBeverage {
/**
* 整个流程处理
* 声明为final以免子类改变这个算法的顺序
*/
final void prepareRecipe() {
// 把水煮沸
boilWater();
// 用热水泡咖啡或茶
brew();
// 把饮料倒进杯子
pourInCup();
// 在饮料内加入适当的调料
addCondiments();
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("boil water. Both.");
}
void pourInCup() {
System.out.println("pour in cup. Both");
}
}
2. 定义实现类
public class Coffee extends CaffeineBeverage{
@Override
void brew() {
System.out.println("Coffee brew.");
}
@Override
void addCondiments() {
System.out.println("Coffee add sugar and milk.");
}
}
public class Tea extends CaffeineBeverage {
@Override
void brew() {
System.out.println("Tea brew.");
}
@Override
void addCondiments() {
System.out.println("Tea add lemon.");
}
}
回调
代码
1. 定义回调接口
public interface ICallback {
void methodToCallback();
}
2.定义B类(接收回调函数者)
public class BClass {
public void process(ICallback callback) {
callback.methodToCallback();
}
}
3.定义A类(传递回调函数者)
/**
* 1.回调跟模板模式一样,也具有复用和扩展的功能.
* 2.回调分为同步回调、异步回调(延迟回调)。
* 同步回调指在函数返回之前执行回调函数、异步回调指的是在函数返回之后执行回调函数。
* 3.从应用场景看,同步模式看起来更像模式模式,异步回调看起来更像观察者模式。
*/
public class AClass {
public static void main(String[] args) {
BClass b = new BClass();
b.process(new ICallback() {
@Override
public void methodToCallback() {
System.out.println("method call back.");
}
});
}
}
回调小结
-
回调跟模板模式一样,也具有复用和扩展的功能。
A
类可以根据实际情况自定义实现回调方法。有具大的操作空间。 -
相对于普通的函数调用来说,回调是一种双向调用关系。A类事先注册某个函数F到B类,A类在调用B类的P函数的时候,B类反过来调用A类注册给它的F函数。即
A调用B,B返过来调用A
。 -
应用举例:
- JdbcTemplate
- addShundownHook()
-
模块模式VS回调
从应用场景上来看,同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。而异步回调跟模板模式有较大差别,更像是观察者模式。
从代码实现上来看,回调和模板模式完全不同。回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。
组合优于继承。实际上,这里也不例外。在代码实现上,回调相对于模板模式会更加灵活,主要体现在下面几点。
- 像 Java 这种只支持单继承的语言,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。
- 回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类。
- 如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,我们只需要往用到的模板方法中注入回调对象即可。
总结
-
模板模式主要用来解决
复用
和扩展
问题。-
复用
把一个算法中不变的流程抽象到父类的模板方法
templateMethod()
中,将可变的部分method1()、method2()
留给子类ContreteClass1
和ContreteClass2
来实现。- Java IO类库中,有很多设计用到了模板模式。如
InputStream、OutputStream、Reader、Writer
。在InputStream
中,read()
函数是一个模板方法,该方法定义了读取数据的整个流程,并且暴露了一个可以由子类定制的抽象方法。 - Java AbstractList类中。
addAll()
函数可以看作模板方法,虽然并未定义abstract
抽象方法,但是函数直接抛出了异常。若要使用该方法,必须重写。
- Java IO类库中,有很多设计用到了模板模式。如
-
扩展
HttpServlet
的service()
方法就是一个模板方法,它实现了整个 HTTP 请求的执行流程,doGet()、doPost()
是模板中可以由子类来定制的部分。实际上,这就相当于Servlet
框架提供了一个扩展点(doGet()、doPost()
方法),让框架用户在不用修改Servlet
框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。又比如
compareTo
方法。让子类扩展该方法的实现。
-
-
模块模式基于继承。对
Java
单继承语言来说,会有较大限制。 -
文章中内容分别来自
Head First 设计模式
、图解设计模式
、设计模式之美(极客学院)