1 模板方法模式介绍
在软件开发中,某个方法的实现需要多个步骤,其中有些步骤是固定的,而有些步骤并不固定,存在可变性。为了提高代码的复用性和系统的灵活性,可以使用一种称为模板方法模式(Template Method Pattern)的设计模式来对这类情况进行设计。在模板方法模式中将实现功能的每一个步骤所对应的方法称为基本方法,而将调用这些基本方法同时定义基本方法的执行次序的方法称为模板方法。
模板方法模式定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。它是结构最简单的行为型设计模式,在其结构中只存在父类与子类的继承关系。
模板方法模式将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。
主要解决: 一些方法通用,却在每一个子类都重新写了这一方法。
何时使用: 有一些通用的方法。
如何解决: 将这些通用算法抽象出来。
关键代码: 在抽象类实现,其他步骤在子类实现。
应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点: 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项: 为防止恶意操作,一般模板方法都加上 final 关键词。
2 模板方法模式详解
2.1 模板方法模式结构
模板方法模式的结构比较简单,其核心是抽象类和其中的模板方法设计,其结构图如下:
由上图可知,模板方法模式包含以下两个角色。
AbstractClass(抽象类):在抽象类中定义了一系列基本操作(Primitive Operations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
ConcreteClass(具体子类):它是抽象类的子类,用于实现在父类中声明的抽象基本操作以及完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
2.2 模板方法模式实现
在实现模板方法模式时,开发抽象类的软件设计师和开发具体子类的软件设计师之间可以进行协作。
模板方法
一个模板方法就是将定义在抽象类中的把基本操作方法组合在一起形成一个总算法或一个总行为的方法。这个模板方法定义在抽象类中,并由子类不加修改地完全继承下来。由于模板方法是具体方法,因此模板方法模式中的抽象层只能是抽象类,而不是接口。
基本方法
基本方法是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为3种,即抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。
抽象方法:一个抽象方法由抽象类声明、由其具体子类实现。
具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
钩子方法:一个钩子方法由一个抽象类或具体类声明并实现,而其子类可能会加以扩展。通常在父类中给出的实现是一个空实现,并以该空实现作为方法的默认实现。当然,钩子方法也可以提供一个非空的默认实现。
抽象类的典型代码如下:
public abstract class AbstractClass {
//模板方法
public void templateMethod() {
primitiveOperation1();
primitiveOperation2();
primitiveOperation3();
}
//基本方法—具体方法
public void primitiveOperation1() {
//实现代码
}
//基本方法—抽象方法
public abstract void primitiveOperation2();
//基本方法—钩子方法
public void primitiveOperation3()
{ }
}
具体子类的典型代码如下:
public class ConcreteClass extends AbstractClass {
public void primitiveOperation2() {
//实现代码
}
public void primitiveOperation3() {
//实现代码
}
}
2.3 模板方法模式应用举例
题目描述
某软件公司要为某银行的业务支撑系统开发一个利息计算模块,利息的计算流程如下:
(1) 系统根据账号和密码验证用户信息,如果用户信息错误,则系统显示出错提示。
(2) 如果用户信息正确,则根据用户类型的不同使用不同的利息计算公式计算利息(如活期账户和定期账户具有不同的利息计算公式)。
(3) 系统显示利息。
现使用模板方法模式设计该利息计算模块。
UML类图
其中,Account充当抽象类角色,CurrentAccount和SavingAccount充当具体子类角色。
package template_method_pattern;
/**
* 账户类,充当抽象类
* @author Cnc_hzf
* @date 2022/5/1 13:17
*/
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 void handle(String account, String password) {
if (!validate(account, password)) {
System.out.println("账号或密码错误!");
} else {
calculateInterest();
display();
}
}
}
package template_method_pattern;
/**
* 定期账户类,充当具体子类
* @author Cnc_hzf
* @date 2022/5/1 13:23
*/
public class SavingAccount extends Account {
@Override
public void calculateInterest() {
System.out.println("按定期利率计算利息!");
}
}
package template_method_pattern;
/**
* 活期账户类,充当具体子类
* @author Cnc_hzf
* @date 2022/5/1 13:22
*/
public class CurrentAccount extends Account {
@Override
public void calculateInterest() {
System.out.println("按活期利率计算利息!");
}
}
package template_method_pattern;
/**
* 客户端测试类
* @author Cnc_hzf
* @date 2022/5/1 13:24
*/
public class Client {
public static void main(String[] args) {
Account account;
account = (Account) XMLUtils.getBean();
account.handle("张无忌", "123456");
}
}
template_method_pattern.CurrentAccount