- 泡茶?泡咖啡?
我们用泡茶和泡咖啡两种行为来引入这一设计模式。
思考一下“泡茶”的过程:
煮水 -> 用沸水泡茶叶 -> 把茶倒进杯子 -> 放点柠檬之类的佐料。
然后再看一下“泡咖啡”的过程:
煮水 -> 用沸水泡咖啡 -> 把咖啡倒进杯子 -> 加牛奶和糖。
如果我们用两个类去描述这两个过程,很明显会有很多重复的代码(例如 Step1 煮水,Step3 倒进杯子),也有很多相似的代码(Step2 冲泡,Step4 加佐料)。
将冲泡的过程看做是一个算法,那么这个算法的主架构是一致的:
煮水 -> 用沸水泡东西 -> 将泡完的东西倒进杯子 -> 加佐料。
将主过程抽象出来,至于泡什么东西,加什么佐料,就交给子类去实现,这就是模板方法模式。
- 模板方法模式:
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
- UML:
除了上述的四个步骤,这里加了个新的方法:customerWantsCondiments(),因为加佐料这个步骤不是必须的。
- 模板方法的组成:
从上面 UML 图,我们能够发现,在抽象类 BeverageDrive 中,一共存在4类方法:
- 模板方法:prepareRecipe(),这是算法的主体,在方法内部会调用其他的方法,一般来说是 public final 的。
- 抽象方法:brew() & addCondiments(),是算法步骤的“个性”,在 BeverageDrive 中声明为 protected abstract,由子类去实现。
- 具体方法:boilWater() & pourInCup(),是算法步骤的“共性”,一般是 private 的。
- 钩子方法:customerWantsCondiments,具有空的或是默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩,选择权在子类手上,一般是 protected 的。
- 代码:
public abstract class BeverageDrive { // Template method public final void prepareRecipe() { boilWater(); brew(); pourInCup(); if (customerWantsCondiments()) { addCondiments(); } } // Abstract method protected abstract void brew(); protected abstract void addCondiments(); // Concrete method private void boilWater() { System.out.println("Boiling water"); } private void pourInCup() { System.out.println("Pouring into cup"); } // Hook method protected boolean customerWantsCondiments() { return true; } }
public final class CoffeeBeverage extends BeverageDrive { @Override public void brew() { System.out.println("Dripping coffee through filter"); } @Override public void addCondiments() { System.out.println("Adding sugar and milk"); } @Override public boolean customerWantsCondiments() { return false; } }
public final class TeaBeverage extends BeverageDrive { @Override public void brew() { System.out.println("Steeping the tea"); } @Override public void addCondiments() { System.out.println("Adding lemon"); } }
- 好莱坞原则:
好莱坞在寻找演员的时候有一个著名的原则:别打电话给我们,我们会打电话给你。
这一点在 OO 设计上,被翻译为:别调用我,我会调用你。
在好莱坞,演员属于低层组件,电影公司属于高层组件。每当电影公司需要演员的时候,都是电影公司打电话通知演员来面试。
换句话说:高层组件对待低层组件的方式是:别调用我,我会调用你。
当我们设计模板方法模式的时候,要时刻记得,让父类去调用子类,不要让子类调用父类方法。
下面看一个不太好的设计:
public abstract class BadBehaviorBeverageDrive { protected void boilWater() { System.out.println("Boiling water"); } protected void pourInCup() { System.out.println("Pouring into cup"); } protected boolean customerWantsCondiments() { return true; } }
public final class BadBehaviorTeaBeverage extends BadBehaviorBeverageDrive { public final void prepareRecipe() { super.boilWater(); steepTeaBags(); super.pourInCup(); if (super.customerWantsCondiments()) { addLemon(); } } private void steepTeaBags() { System.out.println("Steeping the tea"); } private void addLemon() { System.out.println("Adding lemon"); } }
这个设计,一定意义上来说,达到了代码重用的功能,但是违反了好莱坞原则,不能算是一个合格的设计。