模板方法模式
模板方法概述
请客吃饭:(1) 点单 -》 (2) 吃东西 -》 (3) 买单
软件开发:某个方法的实现需要多个步骤(类似“请客”),其中有些步骤是固定的(类似“点单”和“买单”),而有些步骤并不固定,存在可变性(类似“吃东西”)。
模板方法模式:基本方法(“点单”、“吃东西”和“买单”)模板方法( “请客”)
模板方法定义
定义一个操作中算法的框架,而将一些步骤延迟到子类中。
模板方法模式使得子类不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 是一种基于继承的代码复用技术
- 将一些复杂流程的实现步骤封装在一系列基本方法中
- 在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。
模板方法结构
模板方法模式包含以下2个角色:
- AbstractClass(抽象类)
- ConcreteClass(具体子类)
模板方法的实现
- 模板方法 (Template Method)
- 基本方法 (Primitive Method)
- 抽象方法(Abstract Method)
- 具体方法(Concrete Method)
- 钩子方法(Hook Method)
看了一点概念后,可能还是不理解它,因此通过实例来学习
实例学习
某软件公司要为某银行的业务支撑系统开发一个利息计算模块,利息的计算流程如下:
(1) 系统根据账号和密码验证用户信息,如果用户信息错误,则系统显示出错提示。
(2) 如果用户信息正确,则根据用户类型的不同使用不同的利息计算公式计算利息(如活期账户和定期账户具有不同的利息计算公式)。
(3) 系统显示利息。
现使用模板方法模式设计该利息计算模块。
抽象类
// 抽象类里面需要定义基本方法和模板方法
// 抽象方法等待子类去继承
// 模板方法里面要定义操作的步骤,且注意模板方法为final,是不能被重写的。
//账户类:抽象类
public abstract class Account {
//基本方法——具体方法
public boolean validate(String account, String password) {
System.out.println("账号:" + account);
System.out.println("密码:" + password);
if (account.equalsIgnoreCase("张无忌") && password.equalsIgnoreCase("123456")) {
return true;
}
else {
return false;
}
}
//基本方法——抽象方法
public abstract void calculateInterest();
//基本方法——具体方法
public void display() {
System.out.println("显示利息!");
}
//模板方法
public final void handle(String account, String password) {
if (!validate(account,password)) {
System.out.println("账户或密码错误!");
return;
}
calculateInterest();
display();
}
}
具体类
// 具体类,去重写父亲的抽象基本方法
//活期账户类:具体子类
public class CurrentAccount extends Account {
//覆盖父类的抽象基本方法
public void calculateInterest() {
System.out.println("按活期利率计算利息!");
}
}
//定期账户类:具体子类
public class SavingAccount extends Account {
//覆盖父类的抽象基本方法
public void calculateInterest() {
System.out.println("按定期利率计算利息!");
}
}
XML文件
<?xml version="1.0"?>
<config>
<className>designpatterns.templatemethod.SavingAccount</className>
</config>
XML文件读取类
public class XMLUtil {
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
public static Object getBean() {
try {
//创建DOM文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc =builder.parse(new File("./config.xml"));
//获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode=nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//通过类名生成实例对象并将其返回
Class c=Class.forName(cName);
Object obj=c.getConstructor().newInstance();
return obj;
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
客户端调用类
public class Client {
public static void main(String args[]) {
Account account;
account = (Account) XMLUtil.getBean();
account.handle("张无忌","123456");
}
}
输出及分析
账号:张无忌
密码:123456
按定期利率计算利息!
显示利息!
- 客户端读取xml文件生成具体类,让抽象类去引用。
- 调用它的模板方法,分别进行身份验证
- 去子类实现计算利息方法,该方法由父类延迟到子类执行
- 调用抽象类基本方法,完成调用
如果需要更换或增加具体子类,无须修改源代码,只需修改配置文件即可,符合开闭原则
模板方法最终的实现是子类去实现,模板只是定义了一个规范
钩子方法
这里重点讲讲钩子方法:
- 钩子方法在抽象类中且写入模板方法中;
- 它在父类中可能有一点功能,也可能是空方法体积
- 被子类继承之后,子类重写它的方法,这样可以对模板方法插入自己的逻辑,对模板进行扩展
实例类图分析
(1) Account:账户类,充当抽象类
(2) CurrentAccount:活期账户类,充当具体子类
(3) SavingAccount:定期账户类,充当具体子类
(4) Client:客户端测试类
模式优缺点
模式优点
- 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序
- 提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为
- 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行
- 更换和增加新的子类很方便,符合单一职责原则和开闭原则
模式缺点
需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统会更加庞大,设计也更加抽象(可结合桥接模式)
结合桥接模式
为什么结合桥接模式?
因为模板方法的某一个方法的不同实现需要多个子类,这样会导致类很多。
使用方法:
- 在抽象角色(Abstraction)中定义模板方法。
- 抽象原始操作,将需要被重写的方法提取出来。
- 具体实现(ConcreteImplementor)重写这些原始操作提供具体实现。
- 抽象角色注入具体实现,实现桥接。
模板方法的优点:
- 封装了算法的框架,不修改框架即可定制。
桥接模式的优点:
- 分离抽象和实现,扩展性好。
结合之后:
- 实现更加抽象和通用
- 提供了更多扩展点
- 扩展性更好,可以实现多种变化
适用环境
- 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现
- 各子类中公共的行为应被提取出来,并集中到一个公共父类中,以避免代码重复
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制