
设计模式之模板方法模式
封装算法,好让子类可以在任何时候都可以将自己挂接近运算里。
咖啡示例
咖啡师傅训练手册:各位师傅!准备饮料时,请精确地遵循下面的冲泡法
咖啡冲泡法:
- 把水煮沸
- 用沸水冲泡咖啡
- 把咖啡倒进杯子
- 加糖和牛奶
茶冲泡法:
- 把水煮沸
- 用沸水冲泡茶叶
- 把茶倒进杯子
- 加柠檬
快速搞定咖啡和茶的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
public class Coffee {
void prepareRecipe() { boilWater(); brewCoffeeGrinds(); pourInCup(); addSugarAndMilk(); }
private void addSugarAndMilk() { System.out.println("Adding Sugar and Milk"); }
private void pourInCup() { System.out.println("Pouring into cup"); }
private void brewCoffeeGrinds() { System.out.println("Dripping Coffee through filter"); }
private void boilWater() { System.out.println("Boiling water"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
public class Tea {
void prepareRecipe() { boilWater(); steepTeaBag(); pourInCup(); addLemon(); }
private void addLemon() { System.out.println("Adding Lemon"); }
private void pourInCup() { System.out.println("Pouring into cup"); }
private void steepTeaBag() { System.out.println("Steeping the tea"); }
private void boilWater() { System.out.println("Boiling water"); } }
|
抽象 prepareRecipe()
将咖啡和茶的制作共同点抽取出来,让我们每个子类中逐步抽象prepareRecipe()…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
public abstract class CaffeineBeverage {
final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); }
abstract void brew();
abstract void addCondiments();
private void pourInCup() { System.out.println("Pouring into cup"); }
private void boilWater() { System.out.println("Boiling water"); } }
|
改造我们的咖啡和茶类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public class Coffee extends CaffeineBeverage {
@Override protected void brew() { System.out.println("Dripping Coffee through filter"); }
@Override protected void addCondiments() { System.out.println("Adding Sugar and Milk"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public class Tea extends CaffeineBeverage {
@Override protected void brew() { System.out.println("Steeping the tea"); }
@Override protected void addCondiments() { System.out.println("Adding Lemon"); } }
|
认识模板方法
基本上,我们刚刚实现的就是模板方法模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public abstract class CaffeineBeverage { final void prepareRecipe()() { boilWater(); brew(); pourInCup(); addCondiments(); } protected abstract void brew();
protected abstract void addCondiments(); private void pourInCup() { }
private void boilWater() { } }
|
模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
走,泡茶去…..
首先我们需要一个茶对象…
然后我们调用这个模板方法
首先,把水煮沸,由超类进行
接下来,我们需要泡茶,这件事情只有子类才知道怎么做
现在把茶倒进杯子中,所有的饮料做法都一样,所以这件事情发生在超类中
最后,我们加紧调料,由于调料是各个饮料独有的,所以由子类来实现它
定义模板方法模式
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
对模板方法进行挂钩
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类来决定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
public abstract class CaffeineBeverageWithHook {
final void prepareRecipe() { boilWater(); brew(); pourInCup(); if (customerWantsCondiments()) { addCondiments(); } }
protected abstract void brew();
protected abstract void addCondiments();
private void pourInCup() { System.out.println("Pouring into cup"); }
private void boilWater() { System.out.println("Boiling water"); }
boolean customerWantsCondiments() { return true; } }
|
使用钩子
为了使用钩子,我们在子类中覆盖它。在这里,钩子控制了咖啡因饮料是否执行某部分算法;说得更明确一些,就是饮料中是否要加进调料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
public class TeaWithHook extends CaffeineBeverageWithHook {
@Override protected void brew() { System.out.println("Steeping the tea"); }
@Override protected void addCondiments() { System.out.println("Adding Lemon"); }
@Override boolean customerWantsCondiments() { return getUserInput().toLowerCase().startsWith("y"); }
private String getUserInput() { System.out.print("Would you like lemon with your tea (y/n)?");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { return in.readLine(); } catch (IOException e) { e.printStackTrace(); } return "no"; } }
|
执行测试程序
1 2 3 4 5 6 7 8
| public class BeverageTestDrive { public static void main(String[] args) { TeaWithHook teaWithHook = new TeaWithHook(); System.out.println("Make tea..."); teaWithHook.prepareRecipe(); } }
|
1 2 3 4 5 6
| Make tea... Boiling water Steeping the tea Pouring into cup Would you like lemon with your tea (y/n)? y Adding Lemon
|
好莱坞原则
我们有一个新的设计原则,称为好莱坞原则:
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
好莱坞原则和模板方法
好莱坞原则和模板方法之间的连接还算明细:当我们设计模板方法模式时,我们告诉子类,“不要调用我们,我们会调用你”。
总结
- 模板方法定义了算法的步骤,把这些步骤的实现延迟到了子类
- 模板方法模式为我们提供了一种代码复用的重要技巧
- 模板方法的抽象类可以定义具体方法、抽象方法和钩子
- 抽象方法由子类实现
- 钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它
- 为了防止子类改变模板方法中的算法,可以将模板方法声明为final
- 好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用底层模块
- 你将在真实世界代码中看到模板方法模式的许多变体,不要期待它们全都是一眼就可以被你认出的
- 策略模式和模板方法模式都封装算法,一个用组合,一个用继承
- 工厂方法是模板方法的一种特殊版本