引子
例如有两个处理逻辑:泡茶、冲咖啡;
他们的基本流程(算法)是相同的:煮开水、冲泡、倒进杯子、加入调料。只不过具体到个别步骤可能有差异。
如果分成两个类来实现,就会存在重复代码。
——可以将公共的部分(算法)提到父类中;由各个子类实现每个具体步骤。
定义
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
模板方法模式在一个方法中定义算法的框架,而将一些算法步骤延迟到子类中定义。使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。——即,封装算法
类图
public abstract class AbstractClass {
//template method ----final
final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
concreteOperation();
hook();
if( hook2() ){ //钩子可影响算法行为
}
}
protected abstract void primitiveOperation1();
protected abstract void primitiveOperation2();
final void concreteOperation() { ...... }
//父类中可以有“默认不做事的方法”;子类可以视情况决定要不要覆盖它
void hook() { }//empty method
boolean hook2() { return true; }
}
- 为防止子类改变模板方法中的算法,模板方法一般都为final
- 算法中的必需步骤,用abstract;算法中的可选步骤,用hook
- 好莱坞原则:在设计模板方法模式时,我们(高层组件)告诉子类(低层组件)“不要调用我们,我们会调用你”
优点
- 封装不变部分,扩展可变部分
- 提取公共部分代码,方便维护
- 行为由父类控制,子类实现
缺点
子类执行的结果会影响父类的结果,子类影响父类
使用场景
- 多个子类有公用方法,并且逻辑基本相同时
- 重要的、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能由子类实现
- 可用钩子约束算法行为——使用钩子可作为条件控制,影响抽象类中的算法流程
JDK中的模板方法
Array.sort()
// Arrays
public static void sort(Object[] a) {
...
ComparableTimSort.sort(a);
}
// ComparableTimSort
static void sort(Object[] a, int lo, int hi) {
...
binarySort(a, lo, hi, lo + initRunLen);
}
//算法框架
private static void binarySort(Object[] a, int lo, int hi, int start) {
assert lo <= start && start <= hi;
if (start == lo)
start++;
for ( ; start < hi; start++) {
@SuppressWarnings("unchecked")
Comparable<Object> pivot = (Comparable) a[start];
// Set left (and right) to the index where a[start] (pivot) belongs
....
while (left < right) {
int mid = (left + right) >>> 1;
if (pivot.compareTo(a[mid]) < 0) //compareTo这个算法步骤,是由各个Comparable的子类定义的
right = mid;
else
left = mid + 1;
}
....
}
}
Q:这个算法框架并不是设计在父类中,而是在一个工具类中
A:是的,与教科书上模板方法的定义有差异;因为sort要适用于所有数组,所以提供了一个Arrays工具类。但仍然是模板方法模式
InputStream.read()
//算法框架
public int read(byte b[], int off, int len) throws IOException {
...
int c = read();
...
}
//算法步骤由子类实现
public abstract int read() throws IOException;
JFrame.paint()
// JFrame
public void update(Graphics g) {
paint(g);
}
public class MyFrame extends JFrame {
public MyFrame(){
super();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(300,300);
this.setVisible(true);
}
@Override
public void paint(Graphics g){ //重定义算法步骤
super.paint(g);
g.drawString("I rule !", 100, 100);
}
public static void main(String[] args){
MyFrame frame = new MyFrame();
}
}
Applet.init()/start()/stop()/destroy()/paint()
// Applet
public void init() { //什么也不做的hook
}
// Beans
public static Object instantiate(ClassLoader cls, String beanName,
BeanContext beanContext, AppletInitializer initializer)
throws IOException, ClassNotFoundException {
// If it was deserialized then it was already init-ed.
// Otherwise we need to initialize it.
if (!serialized) {
// We need to set a reasonable initial size, as many
// applets are unhappy if they are started without
// having been explicitly sized.
applet.setSize(100,100);
applet.init(); //调用hook
}
}
return result;
}
Applet中的init()/start()/stop()/destroy()/paint()这些方法,都是hook。