实例场景:假如我们要泡一杯热茶,我们得要准备杯子、准备开水、准备茶叶,当我们泡茶的时候,只要将茶和开水放进杯子里面就可以了,好了,大家都知道,每个人泡茶的方法会有所不同,比如准备的杯子大小不同、水的热度不同、茶叶的品种不同,这些每个人都会有每个人的做法,但是将茶和开水放进杯子里面都是一样的吧,哈哈..这个例子中我们运用到了典型的模板方法模式,看一下它的定义吧!

摸板方法(Template Method)模式是一种非常简单而又经常使用的设计模式.先创建一个父类,把其中的一个或多个方法留给子类去实现,这实际上就是在使用摸板模式.所谓的摸板模式可以这样来理解:"在一个类中定义一个算法,但将此算法的某些细节留到子类中去实现.换句话说,基类是一个抽象类,那么你就是在使用一种简单形式的摸板模式.",进一步理解,准备一个抽象类,将部分逻辑以具体方法的形式实现,然后申明一些抽象方法来迫使子类实现剩余的逻辑.不同的子类可以以不同的方法实现这些抽象方法,从而对剩余的逻辑有不同的实现

模板方法的角色

父类角色:提供模板,抽象类,定义了一到多个的抽象方法,以供具体的子类来实现它们;而且还要实现一个模板方法,来定义一个算法的骨架。该模板方法不仅调用前面的抽象方法,也可以调用其他的操作,只要能完成自身的使命。
子类角色:为模板提供实现,实现父类中的抽象方法以完成算法中与特定子类相关的步骤。

进一步理解实例:我们将热茶定义为一个抽象类,里面有准备杯子、准备开水、准备茶叶的抽象方法,让子类去实现,因为每个人泡茶会有所不同,制作茶的方法定义为一个模版,因为大家要泡茶都一样。

代码实现:

package cn.com.template; // 抽象摸板角色 // 定义了一个或多个抽象操作,以便让子类实现。 // 定义并实现了一个模板方法。  public abstract class HotTea { 	//留给子类去实现,抽象操作 	protected abstract void prepareCup(); 	protected abstract void prepareTea(); 	protected abstract void prepareHotWater(); 	 	//模版方法 	public void makeHotTea(){ 		this.prepareCup(); 		this.prepareTea(); 		this.prepareHotWater(); 		System.out.println("将茶叶和热水放进杯子里,一杯热茶泡好了"); 	} } 
package cn.com.template; //具体摸板角色 //实现父类所定义的一个或多个抽象方法。 //每一个抽象模板角色都可以有任意多个具体模板角色与之对应, //而每一个具体模板角色都可以给出这些抽象方法的不同实现。  public class MyHotTea extends HotTea { 	@Override 	protected void prepareCup() { 		System.out.println("我准备了一个很精致的小杯子");  	}  	@Override 	protected void prepareHotWater() { 		System.out.println("我准备了100摄氏度的热水");  	}  	@Override 	protected void prepareTea() { 		System.out.println("我准备的高级的龙井茶");  	} } 
package cn.com.template; //测试,客户端 public class Client { 	public static void main(String[] args) { 		HotTea myHotTea=new MyHotTea(); 		myHotTea.makeHotTea();//ok,自己泡的热茶出来了,慢慢品尝吧 	} } 

代码输出结果:
我准备了一个很精致的小杯子
我准备了100摄氏度的热水
我准备的高级的龙井茶
将茶叶和热水放进杯子里,一杯热茶泡好了
当其它人想泡茶的时候,也只需要继承我们定好的抽象类,然后实现里面的抽象方法,最后调用我们的模板方法就可以啦,是不是很简单呢?

模板方法模式的运用案例

A:JUnit中的TestCase以及它的子类就是一个模板方法模式的例子。在TestCase这个抽象类中将整个测试的流程设置好了,比如先执行Setup方法初始化测试前提,在运行测试方法,然后再TearDown来取消测试设置。但是你将在 Setup、TearDown里面作些什么呢?鬼才知道呢!!因此,而这些步骤的具体实现都延迟到子类中去,也就是你实现的测试类中。

来看下相关的源代码吧。
  这是TestCase中,执行测试的模板方法。你可以看到,里面正像前面定义中所说的那样,它制定了“算法”的框架——先执行setUp方法来做下初始化,然后执行测试方法,最后执行tearDown释放你得到的资源。

public void runBare() throws Throwable {  setUp();  try {   runTest();  }  finally {   tearDown();  } }

这就是上面使用的两个方法。与定义中不同的是,这两个方法并没有被实现为抽象方法,而是两个空的无为方法(被称为钩子方法)。这是因为在测试中,我们并不是必须要让测试程序使用这两个方法来初始化和释放资源的。如果是抽象方法,则子类们必须给它一个实现,不管用到用不到。这显然是不合理的。使用钩子方法,则你在需要的时候,可以在子类中重写这些方法。

protected void setUp() throws Exception {} protected void tearDown() throws Exception {}

在这个里面,setUp与tearDown方法就是我们的抽象方法,只不过它里面是做了空实现,让用户不必全部重写里面的方法,只需要根据需求重新即可,runBare方法是我们的木板方法,在JUint里面自己实现调用了。

B:HttpServlet技术

HttpServlet类提供了一个service()方法.这个方法调用了一个或是多个do方法,完成对客户端发起的请求的处理,这些do方法则是由具体的HttpServlet类提供的.那么这里的service()方法就是一个摸板方法.在里面做了判断,里面的doPost或者doGet方法也是抽象方法,只不过它也做了空实现,让用户不必重写全部方法,而是根据需求重写所需的方法。

模版方法的适用情况

根据上面对定义的分析,以及例子的说明,可以看出模板方法适用于以下情况:
  1) 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
  2) 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。其实这可以说是一种好的编码习惯了。
  3) 控制子类扩展。模板方法只在特定点调用操作,这样就只允许在这些点进行扩展。比如上面runBare()方法就只在runTest前面适用setUp方法。如果你不愿子类来修改你的模板方法定义的框架,你可以采用两种方式来做:一是在API中不体现出你的模板方法;二、将你的模板方法置为final就可以了。
  可以看出,使用模板方法模式可以将代码的公共行为提取出来,达到复用的目的。而且,在模板方法模式中,是由父类的模板方法来控制子类中的具体实现。这样你在实现子类的时候,根本不需要对业务流程有太多的了解。