模板方法模式定义:在一个方法中定义一个算法的骨架,将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
将这个定义进行拆分解释,来建立对于模板方法的一个初步概念。定义中说在一个方法中定义一个算法的骨架,通常我们写的算法都有一个入口函数,在这个入口函数中我们可能需要调用很多其他的方法来完整这个算法(毕竟将所有的代码写在一个大方法里面,从而让一个方法中的代码量达到成百上千行是不提倡的,我们应该让我们的程序更加的模块化,易于管理。),这里的入口函数就是一个算法的骨架,也被称为模板方法,其他程序通过调用这个入口函数来达到算法的执行效果。而在这个入口函数中可能存在许多子方法(这些子方法是用来构成这个算法的),这些子方法如果是子类通用的,就可以只在父类中实现,如果是视不同子类有所不同就需要延迟到子类中去实现。这样就可以不改变整个算法的执行流程,但是又拥有属于每个子类自己特色的行为。
举例说明:(这里用一个宋丹丹的经典小品中的一个笑话来举例)
宋丹丹问:“请问,将大象装冰箱,拢共分几步?”
答:“三步!第一步,将冰箱门打开;第二步,将大象放进去;第三步,将冰箱门关上。”
嘿嘿,将大象放冰箱,与我们平时将一串儿葡萄放入冰箱的步骤相同。但是这只是算法的流程相同,但是在具体实施时还是大有不同的。
我们将这个流程称为模板方法,流程定义如下:
- 开门(open)
- 放入(putIn)
- 关门(close)
具体代码如下:
模板类,其中定义了模板方法
1 /** 2 * 模板类,在这里定义模板方法 3 * @author Apache_xiaochao 4 * 5 */ 6 public abstract class TemplateClass { 7 8 /** 9 * 模板方法,用来定义将物品放入冰箱的流程 10 * 用final修饰来防止子类修改算法流程 11 */ 12 public final void putThingIntoFridge(){ 13 open();17 putIn(); 18 close(); 19 } 20 21 /** 22 * 开启冰箱门的方法 23 */ 24 void open(){ 25 System.out.println("把冰箱门打开!"); 26 } 27 28 /** 29 * 关闭冰箱门的方法 30 */ 31 void close(){ 32 System.out.println("把冰箱门关上!"); 33 } 34 35 /** 36 * 将物品放入冰箱的方法,因为这个方法视具体子类而不同,所以延迟到子类中去实现 37 */ 38 abstract void putIn(); 39 40 }
模板类的导出类:
1 /** 2 * 将大象装冰箱的类 3 * @author Apache_xiaochao 4 * 5 */ 6 public class PutElephant extends TemplateClass { 7 8 /** 9 * 因为大象比较大,所以在放入冰箱的时候,需要切成小块放入冰箱 10 */ 11 @Override 12 void putIn() { 13 System.out.println("大象比较大,将其切成小块装,再放入冰箱!"); 14 } 15 16 }
1 /** 2 * 将葡萄放入冰箱 3 * @author Apache_xiaochao 4 * 5 */ 6 public class PutGrape extends TemplateClass { 7 8 /** 9 * 需要将葡萄放入水果篮,然后再放入冰箱 10 */ 11 @Override 12 void putIn() { 13 System.out.println("葡萄是水果,先用水果篮装起来,再放入冰箱!"); 14 } 15 16 }
驱动类:
1 /** 2 * 测试驱动类 3 * @author Apache_xiaochao 4 * 5 */ 6 public class Driver { 7 8 public static void main(String[] args) { 9 10 //将大象装冰箱 11 PutElephant putElephant = new PutElephant(); 12 putElephant.putThingIntoFridge(); 13 System.out.println(); 14 15 //将葡萄装冰箱 16 PutGrape putGrape = new PutGrape(); 17 putGrape.putThingIntoFridge(); 18 19 } 20 21 }
上面是模板方法模式的一个简单实现,简单的说就是定义一个算法模板,算法的步骤可以使具体的,也可以是抽象的,具体的则在模板类中定义,由子类共享,抽象里就延迟到具体子类中去定义。
我们还可以在模板方法中放入一个钩子方法。这个方法是具体的,但是默认什么事情都不做,或者有默认的实现,子类可以视情况来覆盖这个方法(称为挂钩)。
比如,对上面的例子进行修改一下,假设把东西放入冰箱的方法中提供了一个步骤:在东西放入冰箱之前,由用户确认是否需要封装保鲜膜,代码如下:
1 /** 2 * 模板类,在这里定义模板方法 3 * @author Apache_xiaochao 4 * 5 */ 6 public abstract class TemplateClass { 7 8 /** 9 * 模板方法,用来定义将物品放入冰箱的流程 10 * 用final修饰来防止子类修改算法流程 11 */ 12 public final void putThingIntoFridge(){ 13 open(); 14 if(isAddPlasticWrap()){ 15 addPlasticWrap(); 16 } 17 putIn(); 18 close(); 19 } 20 21 /** 22 * 开启冰箱门的方法 23 */ 24 void open(){ 25 System.out.println("把冰箱门打开!"); 26 } 27 28 /** 29 * 关闭冰箱门的方法 30 */ 31 void close(){ 32 System.out.println("把冰箱门关上!"); 33 } 34 35 /** 36 * 将物品放入冰箱的方法,因为这个方法视具体子类而不同,所以延迟到子类中去实现 37 */ 38 abstract void putIn(); 39 40 /** 41 * 添加保险膜的函数 42 */ 43 void addPlasticWrap(){ 44 System.out.println("为了不串味,添加保鲜膜!"); 45 } 46 47 /** 48 * 这是一个钩子方法 49 * 确定是否添加保鲜膜,默认添加 50 * @return 51 */ 52 boolean isAddPlasticWrap(){ 53 return true; 54 } 55 }
在新的代码中:
/**
* 这是一个钩子方法
* 确定是否添加保鲜膜,默认添加
* @return
*/
boolean isAddPlasticWrap(){
return true;
}
就是一个钩子方法,它的功能是确定是否为食品添加保鲜膜,它有一个默认实现,就是添加,而子类可以覆盖这个方法来决定是否添加保鲜膜。
模板类中的方法分为三类:
- 模板方法:封装算法的方法
- 基本方法:组成算法的方法
- 钩子方法:有默认实现,但是可以被子类覆盖的方法
总结:
- 策略模式和模板方法模式都封装算法,前者用的是组合,后者用的是继承
- 工厂方法是模板方法的一种特殊版本
- 好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用低层模块