使用场景
在一些特定的情形中,整个事务流程中有着固定的某些步骤,例如豆浆的生产过程可简化为:①选取豆材 ②加入配料 ③研磨 ,又例如在去银行办业务,基本的流程为:①挂号排队 ②办理业务 ③服务评分 ,在这一类型的事物中,往往都是有着固定的运行流程,而在流程中不同的部分仅仅只是某一步,例如豆浆的②加入配料,这里可以加入不同的配料,加入什么配料由执行者决定,银行的业务也是同样的道理。基于这类情形,我们可以通过模板模式来设计我们的代码。
介绍
模板模式:
- 一个抽象类定义了公有的模板/算法来执行其内部的方法,它的子类可以重新定义父类中的某一方法,但是执行步骤仍按照父类定义的模板/算法来实现。
- 是一种行为型模式
下面给出一种例子,大概实现的功能是:
在银行ATM自助机执行存/储两种操作。
UML类图如下:
ATM操作的抽象类,定义了基本的四个执行过程:
- 插入进行卡
- 输出密码
- 执行所选业务
- 退出银行卡
同时有一个不可变的use()方法,作为其子类的执行模板,子类必须按照定义的模板来顺序执行相应的方法。
内有一个抽象方法abstract void service(),子类必须重写这个方法来具体实现。
public abstract class AbstractATM {
//模板
public final void use(){
card();
password();
service();
exit();
}
protected void card(){
System.out.println("请放入银行卡");
}
protected void password(){
System.out.println("请输入密码");
}
//由子类实现其具体功能
protected abstract void service();
protected void exit(){
System.out.println("欢迎再次使用");
}
}
子类重写service方法,为存储操作:
public class DepositATM extends AbstractATM {
@Override
protected void service() {
System.out.println("执行存钱业务");
}
}
子类重写service方法,为取钱操作:
public class WithdrawATM extends AbstractATM {
@Override
protected void service() {
System.out.println("执行取钱业务");
}
}
客户端新建一个存钱或取钱的对象,调用use()方法,实现使用ATM存储机的整个过程:
public class Client {
public static void main(String[] args) {
AbstractATM depositATM = new DepositATM();
depositATM.use();
AbstractATM withdrawATM = new WithdrawATM();
withdrawATM.use();
}
}
运行结果如下:
请放入银行卡
请输入密码
执行存钱业务
欢迎再次使用
请放入银行卡
请输入密码
执行取钱业务
欢迎再次使用
钩子方法
若我们在ATM前突然有急事需要马上离开或者不想继续操作了,或者以上面的豆浆为例子,我们并不想要添加配料。
此时,我们添加一个判断方法:
public abstract class AbstractATM {
public final void use(){
card();
password();
//如果反悔不为真,则执行服务
if (!regret()) service();
exit();
}
//添加一个返回的函数,子类可以按情况重写
boolean regret(){
return false;
}
protected void card(){
System.out.println("请放入银行卡");
}
protected void password(){
System.out.println("请输入密码");
}
//由子类实现其具体功能
protected abstract void service();
protected void exit(){
System.out.println("欢迎再次使用");
}
}
相应的子类重写方法(只在存钱类重写了regret方法):
public class DepositATM extends AbstractATM {
@Override
protected void service() {
System.out.println("执行存钱业务");
}
@Override
boolean regret() {
return true;
}
}
同样的客户端调用:
public class Client {
public static void main(String[] args) {
AbstractATM depositATM = new DepositATM();
depositATM.use();
AbstractATM withdrawATM = new WithdrawATM();
withdrawATM.use();
}
}
这时候同样的客户端输出为:
请放入银行卡
请输入密码
欢迎再次使用
请放入银行卡
请输入密码
执行取钱业务
欢迎再次使用
可以发现存钱类并没有执行它的service方法。
这种方法叫做钩子方法,可根据业务需求来重写钩子方法。
总结
优点:
-
封装不变部分,扩展变化部分;
-
便于维护;
-
执行顺序由父类控制,行为由子类实现。
缺点: 每个实现都需要由不同的子类实现,类数量过多。
关键: 父类定义执行步骤(一般由final修饰),子类实现具体行为。
应用: 有固定方法步骤,可执行不同行为的情形。