设计模式(十四):模版方法模式(Template Method Pattern)
模版方法模式是非常常见的模式,并且用户往往使用了模板方法模式而没意思到自己已经使用了这个模式。模板方法模式是行为模式。
生活中在做某些事情时,往往存在重复的步骤,例如用户银行办事,有要办存款,办取款等,但都需要经过 取号,填单,排队,等叫号这些步骤,这些重复的步骤延伸到软件设计里就可以对其进行抽象,在抽象类里定义流程或格式(方法的调用方式(步骤的顺序)),子类继承并且可按需重写方法(步骤)。例如,简历模板,论文模板等。
模板方法模式:使用抽象模板,制定行为的流程规范或步骤。
策略模式:定一系算法,算法之间可替换且互不影响。
状态模式:对象自己内部状态改变而改变自己的行为。
观察者模式:观察外部对象的行为发生改变,接受通知并更新自己的行为。
模式定义
模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
白话:在抽象类里定义一系列方法(可理解为流程步骤),由子类实现,子类可以自定义或重写方法;定义一个模板方法,模板方法里制定了各个方法的调用逻辑,并且子类不可重写只能调用。
注意,模板方法模式的实现方法是从上到下的,也就是说,需要首先给出顶级逻辑,然后给出具体步骤的逻辑。
模板方法模式的设计理念是尽量减少必须由子类置重写(override) 的基本方法的数目。子类可以替换父类的可变部分,但是子类不能改变模板方法所代表的顶级逻辑。
模式分析
模式结构
模板方法模式包含如下角色:
抽象模板类(Abstract Template):负责给出一个算法的轮廓和骨架。它由若干个基本方法和一个模板方法构成。
基本方法:定义一个或多个抽象操作,也叫基本操作,由子类负责具体实现,它们是一个顶级逻辑的组成步骤。
基本方法又可分类三种,由子类实现的抽象方法;由抽象类声明并实现的具体方法,子类并不实现或替换,这些具体方法可以起到工厂方法的作用,又叫做工厂方法;由抽象类声明并实现,而子类会加以扩展的 钩子方法,通常抽象会给出的实现是一个空实现,作为方法的默认实现。
钩子方法 的名字应当以
do
开始,这是熟悉设计模式的 Java 程序设计师的标准做法,在 HttpServlet 中的基本方法也遵循这一命名规则。模板方法:定义并实现了一个模板方法。该模板方法是通常是一个具体方法,它定义了顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现,也可能调用一些具体方法。
具体模板类(Concrete Template):实现抽象模板类中所定义的抽象方法,它们是一个顶级逻辑的组成步骤。
- 每一个抽象模板角色都可以有任意多个具体模板角色,每一个具体模板角色可以自定义抽象方法的实现,从而使得顶级逻辑的实现各不相同。
优缺点
- 优点
- 封装了不变的部分(模板方法),扩展了可变部分(抽象方法)。把不变的部分封装到父类中实现,把可变的部分由子类继承实现,便于子类扩展。
- 在父类中抽象了公共的部分代码,便于代码复用。
- 抽象方法是由子类实现,子类可以通过扩展的方法增加相应的功能,符合开闭原则。
- 缺点
- 对每个不同的实现都需要一个子类,可能导致类的数量增加,系统庞大,设计更加抽象不易理解。
- 子类实现父类的抽象方法,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
应用场景
模板方法模式:子类可以自定义父类的可变部分,但不可以改变模板方法所代表的顶级逻辑,即不可改变的顶级逻辑由父类定义且不可改变。
每当定义一个新的子类时,不要按照控制流程的思路去想,应当按照 责任 的思路去想。即应该考虑那些操作是可以自定义扩展的,那些是不可变的。模板方法可以使这些责任变的清晰。
典型应用
HttpServlet中的应用
Java 语言在很多地方使用过模板方法模式。例如, HttpServlet
技术是建立在模版 方法模式的基础之之上的。
HttpServlet 是个抽象基类,提供了一个 service()
方法,根据请求类型调用对应的 do
方法,同为时 service()
方法 和 七个 do
方法都提供了默认实现(注意:默认实现里没有具体处理)。
这些 do
方法需要由 HttpServlet 的具体子类提供具体实现,在这里,service()
方法便是模版方法,7 个 do 方法便是基本方法(钩子方法),因此,这是典型的模版方法模式。
具体可以对着参考 HttpServlet 源码一起理解。
Spring事务管理中的应用
Spring 事务管理抽象基类 AbstractPlatformTransactionManager 定义了好多几与事务相关的 do
方法,但是空实现,这些do
方法就是基础方法,具体实现由子类提供。
1 | protected abstract void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException; |
AbstractPlatformTransactionManager 中的使用 final
修饰的 getTransaction
和 commit
方法就是模板方法,内部调用了 do
方法。
事务管理的具体子类继承该抽象基类,重写 do
方法,但在做开启事务,和提交操作是调该抽象基类的模板方法。
示例代码
抽象模板类
抽象模板还可能更新前面步骤的返回值来执行后面的算法操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public abstract class AbstractTemplate {
/**
* 模板方法的声明和实现
* 定义轮廓和骨架(调用方法顺序,即逻辑步骤)
*/
public void TemplateMethod() {
doOperation1();
doOperation2();
doOperation3();
}
/**
* 基本方法,交由子类实现
*/
abstract void doOperation1();
/**
* 基本方法,交由子类实现
*/
abstract void doOperation2();
/**
* 基本具体方法
*/
private void doOperation3(){
System.out.println("doOperation3() .....");
}
}具体模板类
1
2
3
4
5
6
7
8
9
10
11public class ConcreteTemplate extends AbstractTemplate {
void doOperation1() {
System.out.println("doOperation1() .....");
}
void doOperation2() {
System.out.println("doOperation2() .....");
}
}客户端调用
1
2
3
4
5
6
7public class TemplateMain {
public static void main(String[] args) {
AbstractTemplate template = new ConcreteTemplate();
template.TemplateMethod();
}
}输出结果
1
2
3doOperation1() .....
doOperation2() .....
doOperation3() .....
其它参考
设计模式(十四):模版方法模式(Template Method Pattern)
http://blog.gxitsky.com/2019/12/31/DesignPatterns-14-Template-Method/