文章目录
1 基本介绍
模版方法模式(Template Method Pattern),又称为 模板模式,它是一种 行为型 设计模式。该模式 定义了算法中各个细节的执行流程(称为 模版方法),而 将细节的具体实现延迟到子类中(称为 抽象方法),使得子类可以在 不改变算法中细节的执行流程 的情况下 重定义细节的具体实现。
2 案例
本案例实现了 统一写各种练习册 的功能,只要继承 Workbook
抽象类并实现其定义的 abstract
方法,就可以使用 .write()
来完成写练习册的操作。
2.1 Workbook 抽象类
public abstract class Workbook { // 练习册
// protected 表示不想被别的包的类随便调用,abstract 表示强制其子类重写
protected abstract void open(); // 打开练习册
protected abstract void solveProblems(); // 完成练习册上的题目
protected abstract void close(); // 合上练习册
// public 表示可以被其他类调用,final 表示其子类不能随便修改本方法的实现
public final void write() { // 写练习册
open();
solveProblems();
close();
}
}
2.2 EnglishWorkbook 类
public class EnglishWorkbook extends Workbook { // 英语练习册
@Override
protected void open() {
System.out.println("#打开英语练习册");
}
@Override
protected void solveProblems() {
System.out.println("#写英语题");
}
@Override
protected void close() {
System.out.println("#合上英语练习册");
}
}
2.3 MathWorkbook 类
public class MathWorkbook extends Workbook { // 数学练习册
@Override
protected void open() {
System.out.println("$打开数学练习册");
}
@Override
protected void solveProblems() {
System.out.println("$写数学题");
}
@Override
protected void close() {
System.out.println("$合上数学练习册");
}
}
2.4 Client 类
public class Client { // 客户端,测试了 英语练习册 和 数学练习册 的 write() 方法
public static void main(String[] args) {
Workbook englishWorkbook = new EnglishWorkbook();
englishWorkbook.write();
System.out.println("================");
Workbook mathWorkbook = new MathWorkbook();
mathWorkbook.write();
}
}
2.5 Client 类的运行结果
#打开英语练习册
#写英语题
#合上英语练习册
================
$打开数学练习册
$写数学题
$合上数学练习册
2.6 总结
在 Workbook
抽象类中的方法分为两种:
- 用
abstract
修饰的:表示强制子类重写、被 模版方法 调用的方法。这就是基本介绍中提到的 抽象方法,在其子类中将被实现。此外,这种方法还可以使用public
修饰,代表这个方法实现了一个完整的操作。 - 用
final
修饰的:表示不能被子类重写、提供给外部调用的方法。这就是基本介绍中提到的 模版方法,它定义了 抽象方法 的执行流程,只要按照这个流程,就能够完成某个完整的操作,在本案例中是 写练习册 这个完整的操作。
如果想要在添加一种练习册,只需要继承 Workbook
抽象类,并实现抽象方法,就可以使用 .write()
方法来完成 写这个练习册 的操作,这就增强了系统的 扩展性。
3 各角色之间的关系
3.1 角色
3.1.1 AbstractClass ( 抽象类 )
该角色负责 定义抽象方法 和 实现模版方法,在 模版方法 中调用 抽象方法。本案例中,Workbook
类扮演了该角色。
3.1.2 ConcreteClass ( 具体类 )
该角色负责 实现 AbstractClass 角色中定义的抽象方法。本案例中,EnglishWorkbook, MathWorkbook
类都在扮演该角色。
3.1.3 Client ( 客户端 )
该角色负责 使用 AbstractClass 角色的模版方法完成具体的业务逻辑。本案例中,Client
类扮演了该角色。
3.2 类图
4 注意事项
- 明确算法执行流程:在抽象类中定义好算法各个细节的执行流程,确保这个流程是不可变的,通常通过在 模板方法 上使用
final
关键字来防止子类重写。 - 合理划分步骤:将算法划分为若干个步骤(细节),每个步骤对应一个方法。
- 最大化代码复用:模版方法模式通过 继承机制 实现了 代码的复用,父类中的 模板方法 和 已实现的具体方法 可以被子类直接使用。
- 避免不必要的子类:如果算法中的可变步骤很少,或者算法本身并不复杂,那么使用模版方法模式可能会导致过度设计。在这种情况下,应该考虑其他更简单的设计模式或方案。
5 在源码中的使用
在 Spring 中,AbstractApplicationContext
抽象类使用到了模版方法模式,其实现了 refresh()
方法,作为 模版方法:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh =
this.applicationStartup.start("spring.context.refresh");
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory); // 模版方法
StartupStep beanPostProcess =
this.applicationStartup.start("spring.context.beans.post-process");
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
initMessageSource();
initApplicationEventMulticaster();
onRefresh(); // 模版方法
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
contextRefresh.end();
}
}
}
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
在 refresh()
中直接调用的 postProcessBeanFactory(), onRefresh()
方法都是 “抽象”方法(这里换做 钩子方法 比较好。钩子方法 指的是在父类中空实现,在子类中具体实现的方法),交给其子类实现,而 refresh()
方法作为 模版方法 调用它们。注意:refresh()
中还间接调用了 “抽象”方法,为了不使结果更复杂,没有介绍这些方法。
6 优缺点
优点:
- 提高系统的灵活性和拓展性:模版方法模式封装了算法中各个细节的执行流程,将算法中 不变的部分(细节的执行流程)和 可变的部分(细节的具体实现)分离开来,从而使细节的具体实现可以根据需要进行变化,提高了算法的 灵活性 和 拓展性。
- 符合开闭原则:模版方法模式符合 开闭原则,即对扩展开放,对修改关闭。通过添加新的子类来扩展算法,而不需要修改现有的类和接口。
缺点:
- 过度设计:在某些情况下,如果算法本身并不复杂,或者算法细节的变化并不频繁,那么使用模版方法模式可能会导致 过度设计。这会增加系统的复杂性和开发成本。
- 依赖继承:模版方法模式依赖于 继承机制 来实现算法的扩展。然而,继承机制本身存在一些缺点,如 破坏封装性、增加类之间的耦合度 等。如果过度依赖继承,可能会导致系统难以维护和扩展。
7 适用场景
- 算法有固定步骤但部分实现可变:当一个算法或过程包含多个步骤,且这些步骤中的 大部分 对于不同的实现来说是 相同 的,但 某些 步骤的具体实现可能因情况而 异 时,可以使用模板方法模式。这样可以将算法的 固定部分 和 可变部分 分离,提高代码的复用性和灵活性。
- 需要在不同子类中重用相同的算法:如果多个子类共享相同的算法流程,但某些步骤的实现因子类而异,则可以使用模板方法模式。通过将算法的固定部分(即算法细节的执行流程)放在父类中,而将可变部分作为抽象方法留给子类实现,可以避免代码重复,同时保持算法的整体结构不变。
8 总结
模版方法模式 是一种 行为型 设计模式,该模式将算法中各个细节的执行流程固定到 模版方法 中,将这些细节的具体实现交给其子类作为 抽象方法 实现(有些时候也可以是 钩子方法),使得子类可以在 不改变算法中细节的执行流程 的情况下 重定义细节的具体实现,从而提高了系统的 灵活性 和 拓展性。不过,该模式很 依赖继承机制,所以需要遵守 里氏替换原则,从而减少 继承 带来的缺陷。