概述
模板方法(Template method)模式,定义了一个算法的步骤,并允许子类为算法中的某些步骤提供实现。也就是说,模板方法模式可以使得子类在不改变算法结构的情况下,自定义算法的实现。
也就是说,当我们要做某一件事的时候,这件事情的步骤是固定的,但每个步骤的具体实现方式是不一定的,模板方法模式就是针对这种场景出现的。拿日常生活中的情况举例,比如我们每天的工作:上班打卡 -> 工作 -> 下班打卡,一般情况下,我们每个人都需要打卡,但我们工作的内容是不同的;比如我们出去吃饭:点餐 -> 吃饭 -> 付款,点餐和付款我们都一样,但吃什么饭就不同了;还有常用的去银行办理业务:取号 -> 办理业务 -> 结束,取号和结束都一样,但办理业务可能是取款,转账,办卡等。这样的场景我们可以举出好多,所谓设计模式源于生活,正所谓如此吧。
模式结构
结构图
模板方法模式是一种结构很简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。首先,我们来简单看下它的UML结构图:
这里对UML图简单说一些,
+
代表public方法,#
代表protected方法,-
代表private方法。
角色
由于模板方法模式是基于继承实现的,所以该模式大致分为两种角色:抽象模板(AbstractTemplate),具体模板(ConcreteTemplate)。
- 抽象模板:
a. 抽象模板类定义了一系列基本操作(PrimitiveOperations),这些操作可以是抽象的,也可以是具体的,每一个操作对应算法的一个实现步骤,而在子类中可以重写或实现这些步骤。
b. 抽象类中实现了一个或多个模板方法,用于定义一个算法的完整步骤。该方法通常情况下会定义为
final
类型,防止子类对算法的步骤进行修改。而在该方法内部,会调用我们的抽象方法或具体方法来一步一步实现整个算法。
- 具体模板
具体模板一般要实现父类所定义的一个或多个抽象方法,来完成我们所需要的功能。通过不同的具体模板类实现父类的抽象方法,从而完成不同功能的实现。
方法
而对应的方法又可以分为以下几种:
- abstractMethod:抽象方法由抽象类声明,然后由子类实现。
- concreteMethod:具体方法,由抽象类声明并实现,一般情况下子类并不去重写覆盖。
- hookMethod:钩子方法,由抽象类声明并实现,然后子类一般会重写覆盖。通常清空下,抽象类提供的是一个默认实现。
- templateMethod:模板方法是通过其他方法进行组合的一种方法,由抽象类声明并实现,通常情况下定义为final类型。
钩子方法
这里的钩子方法和我们平时理解的钩子方法是不同的,平时的钩子可能会用于系统监听,鼠标监听之类的功能,这里的钩子方法是为了将父类的功能延迟到子类实现,并且这里的钩子方法和抽象方法还是有点相似的。
- 由于模板方法是由抽象方法和具体方法来组合起来的,这时候如果我们想改变方法的步骤,比如如果某个条件是true的时候,就执行A方法,否则执行B方法。也就是说,通过钩子可以让我们在实现模板方法的时候,决定哪些步骤是可选的,实现方式则是通过在子类中实现钩子方法来完成这种操作。
- 钩子方法和抽象方法不同的是,抽象类中钩子方法会有一个默认的实现。
- 另外一点,钩子方法应该是我们一开始设计抽象类的实现就确定的,而不是以后添加的。
实例模型
我们使用简单的代码示例来加深一下我们对模板方法模式的理解:
package com.template;
/**
* 模板方法相关
*
* @author zhangkeke
* @since 2018/3/5 15:46
*/
public abstract class AbstractTemplate {
/**
* 模板方法
*/
public final void templateMethod() {
//调用基本方法
abstractMethod();
hookMethod();
concreteMethod();
if (isSuccess()) {
concreteMethod2();
}
}
/**
* 抽象方法,又子类实现其功能
*/
protected abstract void abstractMethod();
/**
* 钩子方法,提供默认实现
*/
protected void hookMethod(){
System.out.println("default implement");
}
/**
* 钩子方法测试
* @return
*/
protected boolean isSuccess() {
return true;
}
/**
* 具体方法,一般情况下在抽象类中声明并实现,并且一般私有
*/
private final void concreteMethod(){
System.out.println("业务相关处理");
}
private final void concreteMethod2(){
System.out.println("测试钩子");
}
}
钩子方法通常有两种形式:
- 一种和抽象方法很像,方法体为空或提供简单实现,由子类来实现,这类方法优于抽象方法的地方就是如果子类没有覆盖,不会编译不通过,而抽象方法则会编译不通过;
- 另一种则是让我们在实现抽象类的实现可选的操作,通常方法名为isXXX()的形式类型;
不过一般情况下,Java中对钩子方法的命名通常是doXXX()形式的,在编写相关程序时,我们尽量按照通用的命名方式来命名。
使用场景
模板方法清晰地划定了某一类业务的变与不变,为一类业务做好了流程框架,为子类提供了公共的代码,并且子类的行为完全由父类来控制,实现了代码的可维护性和可扩展性。父类不容修改,子类可以去扩展,很好地符合了设计模式的开闭原则——对修改封闭,对扩展开放。
所以我们如果在考虑使用模板方法的情况下,首先要理清楚该算法可变的地方和不可变的地方,然后还要保证算法的实现步骤是固定的(比如我们前面提到的银行办理业务的流程一般就是固定的)。搞清楚之后,我们就可以使用模板方法实现算法的不可变的部分,然后在子类中实现可变的部分。其实在Web开发中,我们经常与模板方法打交道,比如学习servlet
过程中的HttpServlet类,最近在学习的Mybatis中的BaseExecutor体系结构等等。
总结
- 模板方法模式是一种比较简单的通过继承来实现的行为型模式;
- 在模板方法模式中,可以围绕变与不变进行设计抽象模板类,相同的放在抽象类中,不同的放在实现类中;
- 为了防止修改模板方法,通常模板类中的模板方法定义为final类型;
- 策略模式和模板方法模式有时候会拿来一起比较,因为两者很相似,有时候可以相互替代。它们都是用于封装算法,不过策略模式一般是基于组合和委托的形式,而模板方法模式是基于继承结构的。
本文参考自:
《Java与模式》
设计模式——模板方法设计模式
Java设计模式之模板方法模式