注意:如果不想浪费时间,请一定要点我。
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
比如考试,每个学生拿到的试卷是一样的,但是不一样的是学生的答案,如下所示:
class PaperA{
public void question1() {
System.out.println("1. 1+1=?"); //每份试卷相同的地方
System.out.println("2"); //和每位同学的回答有关
}
public void question2() {
System.out.println("2. 15+12=?");
System.out.println("26");
}
}
class PaperB{
public void question1() {
System.out.println("1. 1+1=?");
System.out.println("2");
}
public void question2() {
System.out.println("2. 15+12=?");
System.out.println("27");
}
}
以上存在的问题很多,冗余代码、每次修改试卷每位同学的试卷都必须修改等等。
以下介绍的模板方法模式就是解决以上类似的问题的。
定义
定义一个操作中的算法的估计,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
当我们要完成子某一细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现上可能不同时,我们通常考虑使用模板方法模式。
模板方法模式充分体现了好莱坞原则,模板方法模式的主要优点如下:
(1)通过把不变的行为搬移到超类,去除子类中的重复代码。
(2)由于将不变代码搬移到超类,所以有利于代码复用。
(3)将不变的算法移动到超类,把可变算法由子类继承,便于扩展。
模式的主要缺点如下:
(1)对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
(2)父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。模式的结构和实现
(1)抽象类:定义了算法的骨架,实现了一个算法的步骤。
(2)基本方法:是算法中的一个步骤:
a. 抽象方法:在抽象类中声明,由子类实现。
b. 具体方法:在抽象类中已经实现,在具体子类中可以继承或者重写它。
c. 钩子方法:在抽象类中已经实现,根据钩子方法判断是否需要执行某一步骤。
(3)具体子类:实现抽象类中的基本方法,是该算法骨架的一个组成步骤。
该模式的结构图如下所示:

abstract class BasePaper{
public final void question1() {
System.out.println("1. 1+1=?");
answer1();
}
public abstract void answer1();
public final void question2() {
System.out.println("2. 15+12=?");
if(hook()) {
answer2();
}
}
public abstract void answer2();
//钩子函数示例,子类可以覆盖该钩子函数判断是否有必要执行 answer2()
//比如学生不会做,子类可以覆盖该方法返回 false。
public boolean hook1() {
return true;
}
}
class PaperA extends BasePaper{
@Override
public void answer1() {
System.out.println("2");
}
@Override
public void answer2() {
System.out.println("27");
}
}
class PaperB extends BasePaper{
@Override
public void answer1() {
System.out.println("2");
}
@Override
public void answer2() {
System.out.println("25");
}
}
- 模式的一些问题
(1)当创建一个模板方法时,怎么才能知道什么时候使用抽象方法?什么时候使用钩子方法?
当你的子类 "必须" 提供算法中的某个步骤时,就是用抽象方法。如果算法中的某一步骤是可选的,就用钩子方法。子类可以选择实现这个钩子,但不强制这么做。
(2)使用钩子的真正目的是?
钩子有几种用法,如上例钩子可以让子类实现算法中的可选部分。钩子函数还有另一个用法,是让子类有机会能够有机会对模板方法中一些刚刚发生的(或即将发生的)步骤做出反应。