在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
模式定义与特点
- 定义
声明一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。它是一种类行为型模式。
参与角色
抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
- 模板方法:抽象类声明并实现,一般是定义算法的骨架,按某种顺序调用其包含的基本方法。并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。
基本方法:是整个算法中的一个步骤,包含以下几种类型。
- 抽象方法(abstractMethod):在抽象类中申明,可定义多个,由具体子类实现。
- 具体方法(concreteMethod):抽象类中声明并实现,一般不建议子类实现
- 钩子方法(hookMethod):抽象类中声明并给出空实现,子类可以选择性的进行扩展实现。
- 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
- 类结构图
结构代码实现
抽象模板
abstract class AbstractTemplate { //1个模板方法,用于规定具体模板执行的步骤 public void templateMethod() { //具体的方法调用可以调整,就好比先吃菜还是先吃饭,二者都可以 abstractMethod(); concreteMethod(); hookMethod(); } //多个基本方法 //包括1...n个抽象,1个具体,1个钩子方法 public abstract void abstractMethod(); //具体方法,抽象类中声明并实现,不建议子类实现,一般设计成私有的 private void concreteMethod() { System.out.println("父类具体的方法"); } //钩子方法,抽象类中声明并给出空实现,子类可以选择性的实现 public void hookMethod() { } }
具体模板
class ConcreteTemplate1 extends AbstractTemplate{ @Override public void abstractMethod() { System.out.println("这是子类1的具体实现"); } } class ConcreteTemplate2 extends AbstractTemplate { @Override public void abstractMethod() { System.out.println("这是子类2的具体实现"); } }
客户端调用程序
public class TemplateMethodTest { public static void main(String[] args) { //使用第一个模板 AbstractTemplate concreteTemplate1 = new ConcreteTemplate1(); concreteTemplate1.templateMethod(); //使用第二个模板方法 AbstractTemplate concreteTemplate2 = new ConcreteTemplate2(); concreteTemplate2.templateMethod(); } }
模式实现分析
现在我们来实现去银行办理业务的案例,办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分。除了办理具体业务不一样外,其他的流程一样,我们可以进行如下编程。
声明抽象类
abstract class AbstractBankHandler { //准备去办理业务 public void prepareHandler() { //具体的方法调用可以调整,就好比先吃菜还是先吃饭,二者都可以 getNumber(); lineUp(); handler(); evaluate(); hookMethod(); } //多个基本方法 //取号是一样的,所以直接实现,直接实现的方法建议声明为private private void getNumber() { System.out.println("取号"); } //排队,一样的,所以直接实现 private void lineUp() { System.out.println("慢慢长路在排队"); } //办理业务,不一样,子类实现 public abstract void handler(); //评价,不一样,每个人的评价不一样 public abstract void evaluate(); //钩子方法,抽象类中声明并给出空实现,子类可以选择性的实现 public void hookMethod() { } }
声明具体类
class PersonA extends AbstractBankHandler{ @Override public void handler() { System.out.println("存200万"); } @Override public void evaluate() { System.out.println("五星好评"); } } class PersonB extends AbstractBankHandler { @Override public void handler() { System.out.println("取1000万"); } @Override public void evaluate() { System.out.println("态度不好,差评"); } }
客户端调用
public class TemplateMethodTest { public static void main(String[] args) { //使用第一个模板 AbstractBankHandler personA = new PersonA(); personA.prepareHandler(); //使用第二个模板方法 AbstractBankHandler personB = new PersonB(); personB.prepareHandler(); } }
通过上面程序,我们知道,对于流程相同,子类实现不同的操作我们可以采用模板方法模式来实现,这大大的提高了我们代码的复用率。
总结
优点
- 提高代码复用性,将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中
- 实现了反向控制, 比较灵活。因为有钩子方法,因此,子类的实现也可以影响父类中主逻辑的运行。但是,在灵活的同时,由于子类影响到了父类,违反了里氏替换原则,也会给程序带来风险。这就对抽象类的设计有了更高的要求。
缺点
- 引入了抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂度。
应用场景
- 在多个子类拥有相同的方法,并且这些方法逻辑相同时,可以考虑使用模版方法模式。在程序的主框架相同,细节不同的场合下,也比较适合使用这种模式。