这是 2020 年 7 月整理的设计模式专题第十一篇。模板方法模式是”骨架复用”的代表——把不变的流程写死,把可变的步骤留给子类。像做菜:流程都是”洗菜→切菜→炒菜→装盘”,但每道菜的具体步骤不同。
简单说明
模板方法模式(Template Method Pattern):定义一个算法的骨架,一次性实现算法不变的部分,并允许子类为一个或者多个步骤提供实现,实现可变的部分。
- 模板方法提供扩展性和复用性,符合开闭原则。
模板方法的构成
模板方法模式的构成,可以由下面几种方法组合实现:
| 方法类型 | 作用 | 示例 |
|---|---|---|
| 模板方法(主方法) | 定义算法骨架,控制流程 | makeCourse() |
| 具体方法 | 定义不会变动的方法 | makePPT()、makeVideo() |
| 钩子方法 | 不强制子类实现,一般用于触发其它逻辑的调用 | other() |
| 抽象方法 | 由子类实现的方法 | price() |
实现
/**
* 描述:课程(模板类)
*/
public abstract class AbstractCourse {
/**
* 制作课程:定义的算法骨架,子类可以使用,不能修改
*/
protected final void makeCourse() {
makePPT();
makeVideo();
price();
other();
}
final void makePPT() {
System.out.println("制作PPT");
}
final void makeVideo() {
System.out.println("制作视频");
}
public abstract int price();
protected int other() {
return 0; // 钩子方法:默认空实现,子类可按需覆盖
}
}
/**
* 描述:Java 课程:只实现可变的部分
*/
public class JavaCourse extends AbstractCourse {
@Override
public int price() {
return 200;
}
}
/**
* 描述:前端课程:实现价格 + 覆盖钩子方法加送资源
*/
public class FECourse extends AbstractCourse {
@Override
public int price() {
return 100;
}
@Override
protected int other() {
System.out.println("赠送前端资料包"); // 钩子方法触发额外逻辑
return 1;
}
}
流程控制:调用 makeCourse() 时,固定执行”制作PPT → 制作视频 → 定价 → 钩子”,其中 price() 和 other() 由子类决定。
为什么 makeCourse() 要加 final:模板方法定义的是整个流程骨架,不允许子类重写流程,只允许子类实现流程中的某个步骤——这是模板方法的核心约束。
钩子方法的价值
钩子方法让子类能”参与”流程,但不强制:
- 默认空实现:子类不覆盖也不影响流程;
- 可选扩展点:子类想加逻辑就覆盖,不想加就忽略;
- 例如上面的
other():JavaCourse 不覆盖,流程照常;FECourse 覆盖后多送资料包。
适用场景与优缺点
适用场景:
- 有多个子类,它们有相同的算法步骤,但部分步骤实现不同;
- 需要控制子类扩展(把骨架锁死,只开放可变的点);
- 抽取公共代码到父类,消除重复。
优点:
- 复用性好:公共逻辑放在父类,子类只写差异部分;
- 扩展性好:新增实现只需继承 + 覆盖可变步骤,符合开闭原则;
- 反向控制:父类定义流程,子类填充细节(好莱坞原则:“Don’t call us, we’ll call you”)。
缺点:
- 子类越多,父类骨架的修改成本越高(牵一发动全身);
- 抽象程度提升,增加理解难度。
现实中的应用
| 场景 | 说明 |
|---|---|
| Spring JdbcTemplate | 固定”获取连接→执行 SQL→处理结果→关闭”,可变部分交给回调 |
| Servlet HttpServlet | doGet/doPost 由子类实现,service() 是模板方法 |
| AbstractList / AbstractMap | 骨架实现,子类只需实现少数抽象方法 |
| JUnit | 测试执行流程固定,setUp/tearDown 是钩子 |
小结
- 模板方法模式定义算法骨架,固定不变部分,开放可变部分给子类。
- 四种方法:模板方法(骨架)、具体方法(固定)、钩子方法(可选扩展)、抽象方法(必须实现)。
- 模板方法通常加 final,禁止子类改写流程。
- 体现好莱坞原则:父类控制流程,反向调用子类实现。
- 一句话:流程我定死,细节你来填。