设计模式学习笔记(二十五):模板方法模式
1 概述
1.1 引言
模板方法模式是结构最简单的行为型设计模型,在其结构中只存在父类与之类之间的继承关系,通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。模板方法提供了一个模板方法来定义算法框架,而某些具体步骤的实现可以在其子类中完成。
1.2 定义
模板方法模式:定义一个操作中算法的框架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。
模板方法模式是一种类行为型模式。
1.3 结构图
1.4 角色
AbstractClass
(抽象类):抽象类中定义了一系列基本操作,这些操作是具体的也可以是抽象的,每一个基本操作对应算法的一个步骤,在子类中可以重定义或实现这些步骤,同时抽象类实现了一个模板方法,定义一个算法的框架ConcreteClass
(具体子类):实现父类中的抽象基本方法,或者覆盖父类中具体基本操作
1.5 模板方法与基本方法
1.5.1 模板方法
模板方法是在抽象类中定义的,把基本操作方法组合成一个总算法或总行为的方法。模板方法在抽象类中定义,并由子类不加以修改完全继承。模板方法是一个具体方法,给出了一个顶层逻辑框架,而逻辑的组成步骤在抽象类中可以是具体方法,也可以是抽象方法。另外由于模板方法是具体方法,因此抽象层只能实现为抽象类而不能是接口。
1.5.2 基本方法
基本方法是实现算法的各个步骤,是模板方法的组成部分。基本方法又可以分为三种:
- 抽象方法:抽象方法就是在抽象类中声明并由子类实现的方法
- 具体方法:具体方法可以由抽象类实现,或者由子类覆盖实现
- 钩子方法:钩子方法可以由抽象类实现,子类可以加以扩展
在模板方法模式中,钩子方法一般有两类:
- 第一类钩子方法是可以与一些具体步骤挂钩,以实现在不同条件下执行模板方法的不同步骤,这类方法一般返回
boolean
,方法名一般为isXXX
- 第二类钩子方法是实现体为空的具体方法,子类可以根据需要覆盖或者继承这些钩子方法
2 典型实现
2.1 步骤
- 定义抽象类:声明模板方法以及基本方法
- 定义模板方法:模板方法是抽象类中的具体方法,按照实际需要将基本方法进行组合
- 定义基本方法:定义抽象方法,具体方法以及钩子方法,确定好哪些方法交由抽象类实现,哪些方法交由子类实现以及拥有哪些钩子方法
- 定义具体子类:实现抽象类的抽象方法,按照需要对钩子方法或者具体方法进行覆盖
2.2 抽象类
abstract class AbstractClass
{
public void templateMethod()
{
primitiveOperation1();
primitiveOperation2();
if(primitiveOperation3())
System.out.println("符合钩子方法条件");
else
System.out.println("不符合钩子方法条件");
primitiveOperation4();
}
public void primitiveOperation1()
{
System.out.println("抽象类具体方法");
}
//抽象类抽象方法
abstract public void primitiveOperation2();
//第一类钩子方法
public boolean primitiveOperation3()
{
return false;
}
//第二类钩子方法
public void primitiveOperation4()
{
}
}
首先定义了模板方法,作为客户端操作的入口。模板方法中对基本方法进行了组合,这里声明了四个基本方法:
- 第一个是抽象类的具体方法:这是所有子类都拥有的相同实现的方法,不应该被子类覆盖
- 第二个是抽象类的抽象方法:子类需要实现该方法以实现变化
- 第三个是第一类钩子方法:这类钩子方法返回一个
boolean
,可以用于控制是否执行某个步骤,子类可以通过这类钩子方法对模板方法的执行过程进行限制,比如如果不想执行某个步骤可以永远返回false
- 第四个是第二类钩子方法:这类钩子方法中父类提供一个空实现,子类选择性进行覆盖
2.3 具体子类
class ConcreteClass extends AbstractClass
{
public void primitiveOperation2()
{
System.out.println("子类具体方法");
}
public boolean primitiveOperation3()
{
return true;
// 如果想钩子方法返回false可以不实现该方法
// 因为父类默认返回false
// return false;
}
public void primitiveOperation4()
{
System.out.println("子类覆盖父类第二类钩子方法");
}
}
这里子类实现了抽象类的抽象方法,同时覆盖了两类钩子方法。
2.4 客户端
public static void main(String[] args)
{
AbstractClass class1 = new ConcreteClass();
class1.templateMethod();
}
客户端调用很简单,创建一个具体类对象并执行其中的模板方法即可。
输出:
3 实例
为银行业务开发一个利息计算模块,该计算模块拥有标准流程:系统验证用户信息,接着判断用户状态,正常状态用户能计算利息,受限制状态用户无法计算利息,最后显示利息,使用模板方法模式进行设计。
设计如下:
- 抽象类:
Account
- 具体方法:
validate(String name)
- 抽象方法:
calculate()
- 钩子方法:
canCalculate()
+display()
- 具体子类:
NormalAccount
+RestrictedAccount
首先是抽象类的设计:
abstract class Account
{
public void handle(String name)
{
if(validate(name))
{
if(canCalculate())
{
calculate();
}
display();
}
else
{
System.out.println("用户名非法");
}
}
public boolean validate(String name)
{
return "1".equals(name);
}
//抽象类抽象方法
abstract public void calculate();
//第一类钩子方法
public boolean canCalculate()
{
return true;
}
//第二类钩子方法
public void display()
{
}
}
首先通过validate()
验证用户名,接着根据第一类钩子方法canCalculate()
判断能否计算利息,然后调用抽象计算方法calculate()
进行计算,最后无论能够计算利息都会调用第二类钩子方法display()
。
具体子类代码如下:
class NormalAccount extends Account
{
public void calculate()
{
System.out.println("正常状态用户计算利息");
}
public void display()
{
System.out.println("正常状态用户显示利息");
}
}
class RestrictedAccount extends Account
{
public boolean canCalculate()
{
return false;
}
public void calculate()
{}
public void display()
{
System.out.println("受限状态用户无法计算利息");
}
}
正常状态用户中实现抽象方法calculate()
,并覆盖第二类钩子方法display()
,对于受限状态用户,覆盖了第一类钩子方法canCalculate()
,永远返回false
,同时对抽象方法calculate
提供空实现,最后也对第二类钩子方法display
进行了覆盖,提示"无法计算利息"
。
4 主要优点
- 形式化算法:模板方法模式在父类中形式化地定义一个算法,而由子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序
- 代码复用:模板方法模式是一种代码复用技术,提取公共行为并放在父类中,通过子类实现不同的行为
- 实现反向控制:模板方法模式可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否执行
- 增加子类方便:模板方法模式中可通过子类覆盖父类的基本方法,不同子类可以提供基本方法的不同实现,更换以及增加新的子类很方便
5 主要缺点
- 子类数量多:模板方法模式需要为每一个基本方法的不同实现提供一个子类,如果父类可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象
6 适用场景
- 对一些复杂算法进行分割,将其算法中固定不变的部分设计为模板方法和父类方法,而一些改变的细节由子类实现,也就是一次性实现算法中不变部分,并将可变部分交由子类实现
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复
- 需要通过子类决定父类算法中某个步骤是否执行,实现子类对父类的反向控制