设计模式札记08-模板方法模式
设计模式笔记08-模板方法模式
下面是茶
我们发现了重复的代码,这是好现象。这表示我们需要清理一下设计了。在这里,既然茶和咖啡是如此的相似,似乎我们应该将共同的部分抽出来,放进一个基类中。
最后,我们需要处理咖啡和茶类了。这两个类现在都是依赖超类(咖啡因饮料)来处理冲泡法,所以只需要自行处理冲泡和添加调料部分。
设计模式笔记08-模板方法模式
1 引言
直到目前,我们的议题都绕着封装转,我们已经封装了对象创建、方法调用、复杂接口、鸭子、披萨。接下来呢?我们将要深入封装算法块,好让子类可以在任何时候都可以将自己挂接进运算里。我们甚至会在本章学到一个受到好莱坞影响而启发的设计原则。
2 正文
2.1 多来点咖啡因吧
有些人没有咖啡就活不下去,有些人则离不开茶。两者的共同成分是什么,当然是咖啡因啦。
让我们扮演“代码师傅“,写一些代码来创建咖啡和茶。
下面是咖啡:
public class Coffee { /* * 这是我们的咖啡冲泡方法,直接取自训练手册。 * 每个步骤都被实现在分离的方法中。 * 这里每个方法都实现了算法中的一个步骤:煮沸水、冲泡咖啡、把咖啡倒进杯子、加糖和加奶。 */ void prepareRecipe() { boilWater(); brewCoffeeGrinds(); pourInCup(); addSugarAndMilk(); } public void boilWater() { System.out.println("Boiling water"); } public void brewCoffeeGrinds() { System.out.println("Dripping Coffee through filter"); } public void pourInCup() { System.out.println("Pouring into cup"); } public void addSugarAndMilk() { System.out.println("Adding Sugar and Milk"); } }
下面是茶
public class Tea { /* * 这看起来和前一页的咖啡的实现很像,其中第2和第4个步骤不一样,但基本上是相同的冲泡法。 */ void prepareRecipe() { boilWater(); steepTeaBag(); pourInCup(); addLemon(); } public void boilWater() { System.out.println("Boiling water"); } public void steepTeaBag() { System.out.println("Steeping the tea"); } public void addLemon() { System.out.println("Adding Lemon"); } public void pourInCup() { System.out.println("Pouring into cup"); } }
我们发现了重复的代码,这是好现象。这表示我们需要清理一下设计了。在这里,既然茶和咖啡是如此的相似,似乎我们应该将共同的部分抽出来,放进一个基类中。
现在我们有了新的prepareRecipe()方法,但是需要让它能够符合代码。要想这么做,我们先从CaffeineBeverage(咖啡因饮料:茶和咖啡)超类开始。
public abstract class CaffeineBeverage { /* * 现在,用同一个prepareRecipe()方法来处理茶和咖啡。 * prepareRecipe()被声明为final,因为我们不希望子类覆盖这个方法。 * 我们将步骤2和步骤4泛化为brew()和addCondiments()方法。 */ final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } /* * 因为咖啡和茶处理这些方法的做法不同,所以这两个方法必须被声明为抽象,由子类来实现具体方法。 */ abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("Boiling water"); } void pourInCup() { System.out.println("Pouring into cup"); } }
最后,我们需要处理咖啡和茶类了。这两个类现在都是依赖超类(咖啡因饮料)来处理冲泡法,所以只需要自行处理冲泡和添加调料部分。
2.2 认识模板方法
prepareRecipe()是我们的模板方法。为什么?
因为:
1、毕竟它是一个方法。
2、它用作一个算法的模板,在这个例子中,算法是用来制作咖啡因饮料的。
在这个模板中,算法内的每一个步骤都被一个方法代表了。某些方法是由这个类(超类)来处理的,某些方法则是由子类来处理。
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
2.3 对模板方法进行挂钩
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。
钩子有好几种用途,让我们先看其中一个,稍后再看其他几个:
public abstract class CaffeineBeverageWithHook { /* * 我们加上了一个小小的条件语句,而该条件是否成立,是由一个具体方法customerWantsCondiments()决定的。 * 如果顾客“想要”调料,只有这时我们才调用addCondiments()。 */ void prepareRecipe() { boilWater(); brew(); pourInCup(); if (customerWantsCondiments()) { addCondiments(); } } abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("Boiling water"); } void pourInCup() { System.out.println("Pouring into cup"); } /* * 我们再这里定义了一个方法,(通常)是空的缺省实现。这个方法只会返回true,不做别的事情。 * 这就是钩子,子类可以覆盖这个方法,但不见得一定要这么做。 */ boolean customerWantsCondiments() { return true; } }
2.4 好莱坞原则
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
好莱坞原则可以给我们一种防止“依赖腐败“的方法。当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞定系统是如何设计的。
在好莱坞原则之下,我们允许低层组件讲自己挂钩到系统上,但是高层组件决定什么时候和怎么样使用这些低层组件。换句话说,高层组件对待低层组件的方式是”别调用我们,我们会调用你“。
2.5 用模板方法来排序
实现compareTo()方法即可。
3 本章小结
模板方法从字面意思就很好理解,把握住新学的好莱坞原则,避免依赖腐败,这个就是本章的精髓。