概述:
定义一个操作中的算法的骨架,而将步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构,即可重定义算法的某些特定步骤。
模板方法非常常见,对创建框架来说,由框架控制如何做事情,而由你(使用这个框架的人)指定框架算法中每个步骤的细节。(可以想想单元测试的框架JUnit的实现。)
模式中的角色:
- 抽象类(AbstractClass):实现了模板方法,定义了算法的骨架。
- 具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法。
模板方法模式代码实现(Java):
举个例子,以准备去学校所要做的工作(prepareGotoSchool)为例,假设需要分三步:穿衣服(dressUp),吃早饭(eatBreakfast),带上东西(takeThings)。学生和老师要做得具体事情肯定有所区别。
package com.template.pattern;
/**
* 抽象类AbstractClass
*/
public abstract class AbstractPerson {
// 抽象类定义整个流程骨架
public void prepareGotoSchool() {
dressUp();
eatBreakfast();
takeThings();
}
// 以下是不同子类根据自身特性完成的具体步骤
protected abstract void dressUp();
protected abstract void eatBreakfast();
protected abstract void takeThings();
}
package com.template.pattern.vo;
import com.template.pattern.AbstractPerson;
/**
* 具体类ConcreteClass:学生
*/
public class StudentVo extends AbstractPerson {
@Override
protected void dressUp() {
System.out.println("穿校服");
}
@Override
protected void eatBreakfast() {
System.out.println("吃妈妈做好的早饭");
}
@Override
protected void takeThings() {
System.out.println("背书包,带上家庭作业和红领巾");
}
}
/**
* 具体类ConcreteClass:老师,做的事情不一样,但是步骤(模版方法)一样
*/
public class TeacherVo extends AbstractPerson {
@Override
protected void dressUp() {
System.out.println("穿工作服");
}
@Override
protected void eatBreakfast() {
System.out.println("做早饭,照顾孩子吃早饭");
}
@Override
protected void takeThings() {
System.out.println("带上昨晚准备的考卷");
}
}
/**
* Main方法
*/
public class MainMethod {
/**
* 调用
*/
public static void main(String[] args) {
System.out.println("学生:");
StudentVo sVo = new StudentVo();
sVo.prepareGotoSchool();
// ---------------------------
System.out.println("老师:");
TeacherVo tVo = new TeacherVo();
tVo.prepareGotoSchool();
// 或者
// AbstractPerson aSVo = new StudentVo();
// aSVo.prepareGotoSchool();
// AbstractPerson aTVo = new TeacherVo();
// aTVo.prepareGotoSchool();
}
}
对模板方法进行挂钩:
钩子(hook)是一种被声明在抽象类中的方法,但只有空的或者默认的实现。
钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。
某些步骤是可选的,所以可以将这些步骤实现成钩子,而不是实现成抽象方法,这样就可以让抽象类的子类的负荷减轻。
比如,也可以利用钩子做条件控制,影响抽象类中的算法流程:钩子方法在抽象类中有默认实现返回true,放在抽象类的if条件语句中,子类可以覆盖也可以不覆盖这个钩子方法。
具体实现可以参考下面的url,就是写了个方法,返回true,false,表示是否执行某个方法,进行判断
JUnit中的模板方法模式体现:
以JUnit3为例,JUnit3中,所有的测试类都要继承于TestCase,这个基类中规定了方法调用的顺序,所以每次测试时都是先执行setUp(),再执行测试方法,最后执行tearDown()。
可以查看JUnit3的源代码:
/**
* Runs the bare test sequence.
* @exception Throwable if any exception is thrown
*/
public void runBare() throws Throwable{
setUp();
try{
runTest();
} finally {
tearDown();
}
}
模式总结
- 优点
- 模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
- 子类实现算法的某些细节,有助于算法的扩展。
- 通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
- ——————————————————————–
- 容易扩展。一般来说,抽象类中的模版方法是不易反生改变的部分,而抽象方法是容易反生变化的部分,因此通过增加实现类一般可以很容易实现功能的扩展,符合开闭原则。
- 便于维护。对于模版方法模式来说,正是由于他们的主要逻辑相同,才使用了模版方法,假如不使用模版方法,任由这些相同的代码散乱的分布在不同的类中,维护起来是非常不方便的。
- 比较灵活。因为有钩子方法,因此,子类的实现也可以影响父类中主逻辑的运行。但是,在灵活的同时,由于子类影响到了父类,违反了里氏替换原则,也会给程序带来风险。这就对抽象类的设计有了更高的要求。
- 缺点
- 每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
- 适用场景
- 在某些类的算法中,用了相同的方法,造成代码的重复。
- 控制子类扩展,子类必须遵守算法规则。
代码下载
参考:
- http://www.cnblogs.com/mengdd/archive/2013/04/14/3020577.html
- http://www.cnblogs.com/mengdd/archive/2013/05/04/3059706.html
- http://rjx2008.iteye.com/blog/340362
- http://blog.youkuaiyun.com/zhengzhb/article/details/7405608