本文出自 http://blog.csdn.net/shuangde800
—— 工厂方法是模板方法的一种特殊版本。
——如果你只想掌握一种设计模式,那么它就是模板方法!
走进模板方法
我们知道泡咖啡和泡茶的步骤是很类似的:
咖啡泡法:
1. 把水煮沸
2. 用沸水冲泡咖啡
3.把咖啡倒进杯子
4.加糖和咖啡
泡茶方法:
1.把水煮沸
2.用沸水侵泡茶叶
3.把茶倒进杯子
4.加柠檬
如何设计这两种饮料的类?
一个简单的方法是直接分别针对茶和咖啡设计一个类
咖啡Coffe类里面实现方法: boilWater(); brewCoffeeGrinds(); pourInCup(); addSugarAndMilk(); 茶Tea类里面实现方法: boilWater(); steepTeaBad(); pourInCup(); addLemon();
但是这样的话,将会有重复代码,不符合设计原则。
1. Coffee和Tea将主导一切,它们控制了算法
2. Coffee和Tea之间存在着重复的代码
3. 对于算法所做的代码改变,需要打开子类修改许多地方
4. 由于类的组织方式不具有弹性,所以如果新加入种类的咖啡因因类需要做很多工作
5. 算法的只是和它的实现会分散在许多类中
我们可以发现方法1和3是一样的,而2,4步虽然稍有区别,但是差异也不大。因为侵泡和冲泡差别不大,所以我们大可以给它一个新的方法名称:brew(),然后不管是茶还是咖啡都用这个方法。类似地,加糖和牛奶以及加柠檬也是类似的:都是加入调料。所以变成新方法:addCondiments()
这样,我们可以给茶和咖啡定义一个共同的抽象类,然后再分别继承实现茶和咖啡的类
抽象类CaffeineBaverage(咖啡因饮料)中, prepareRecipe()方法是把泡茶(咖啡)的动作组合起来
boliWater(泡水)和pourlnCup(倒杯)方法因为是相同的,所以定义在超类中,并且可以直接实现。
而brew(冲泡)和addCondiments(加调料)因为不同东西方法不太一样,所以应定义为抽象方法,在具体子类中实现。
定义抽象类的代码:
// 都是咖啡因饮料,所以把抽象类命名为CaffeineBeverage public abstract class CaffeineBeverage { // 用这个方法来处理泡茶和咖啡的步骤 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"); } }
然后我们就可以分别实现子类咖啡和茶的具体做法了:
// 茶 public class Tea extends CaffeineBeverage { // 实现父类的抽象方法 // 是自己特有的动作 public void brew() { System.out.println("Steeping the tea"); } public void addCondiments() { System.out.println("Adding Lemon"); } }
咖啡:
// 咖啡 public class Coffee extends CaffeineBeverage { public void brew() { System.out.println("Dripping Coffee through filter"); } public void addCondiments() { System.out.println("Adding Sugar and Milk"); } }
这样做的好处:
1. 由抽象类CaffeineBeverage主导一切,它拥有算法,而且保护这个算法
2. 对子类来说,CaffeineBeverage类的存在,可以将代码的复用最大化
3. 算法只存在一个地方,所以容易修改。
4. 这个模板方法提供了一个框架,可以让其它的咖啡因因类插进来。新的咖啡因饮料只需要实现自己的方法就可以了
5. CaffeineBeverage专注在算法本身,而由子类提供完整的实现
OK了,其实上面实现的东西 就叫做模板方法模式。
定义模板方法模式
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法是的子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板就是一个方法,更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由其子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。
模板方法的挂钩
钩子是一种被申明在抽象类中的方法,但是只有空的或者默认的实现。
钩子的存在,可以让子类有能力对算法的不同点进行挂钩。而要不要挂钩也由子类决定。
我们来看其中一个实例:带钩的咖啡因饮料类
// 带钩的咖啡因饮料类 public abstract class CaffeineBeverageWithHook { // 这个方法声明为final,防止子类改变模板方法中的算法 void final 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; } }
使用钩子
为了使用钩子,要在子类中覆盖钩子方法。
我们可以让子类实现钩子的一个功能:询问客户是否要加调料?
public class CoffeeWithHook extends CaffeineBeverageWithHook { public void brew() { System.out.println("Dripping Coffee through filter"); } public void addCondiments() { System.out.println("Adding Sugar and Milk"); } // 覆盖了这个“钩子”,提供自己的功能 public boolean customerWantsCondiments() { // 获取用户的输入 String answer = getUserInput(); if (answer.toLowerCase().startsWith("y")) { return true; } else { return false; } } private String getUserInput() { String answer = null; System.out.print("Would you like milk and sugar with your coffee (y/n)? "); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { answer = in.readLine(); } catch (IOException ioe) { System.err.println("IO error trying to read your answer"); } if (answer == null) { return "no"; } return answer; } }
设计原则
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你们
在好莱坞原则下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用我们,我们会调用你们”。
这个原则可以给我们一种防止“依赖腐败”的方法。当高层组件以来低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖低层组件时,依赖腐败就发生了。这种情况下,没有人可以轻易地搞懂系统是如何设计的。
好莱坞原则与模板方法:
当我们设计模板方法时,我们告诉子类“不要调用我们,我们会调用你”
在上面咖啡与茶的例子中,抽象类CaffeineBaverage是我们的高层组件,它能够控制冲泡法的算法,只有在需要子类实现某个方法时,才调用子类。而子类只提供实现的pourInCup和addCondiments方法,如果没有被调用,子类绝对不会直接调用抽象类。
问题
创建一个模板方法时,怎样才能知道什么时候该使用抽象方法,什么时候使用钩子呢?
答:当你的子类“必须”提供算法中某个方法或步骤的实现时,就使用抽象方法。如果算法的这个部分是可选的,就用钩子。如果是钩子的话,子类可以选择实现
使用钩子的真正目的是什么?
钩子有几种用法。如,钩子可以让子类实现算法中的可选部分,或者钩子对于子类的实现并不重要时,子类可以不理会这个钩子。钩子的另一个用法是,让子类能够有机会对模板方法中的某些即将发生的(或刚刚发生的)步骤作出反映,例如,在屏幕上显示数据等。
实际应用中的模板方法
模板方法几乎无处不在 ,这里举几个例子:
Java中的Array类的sort()模板方法,要实现comparTo();
Java中的Swing窗口程序大量的使用了模板方法:Swing中的JFrame,它是一个最基本的Swing容器,继承了一个paint()方法,这是一个钩子,它不做什么事情,通过覆盖paint(),你可以将自己的代码插入到JFrame的算法中,显示出自己想要的画面。
appleet,是一个能在网页上面执行的小程序,也提供了大量的钩子。