文章目录
-
- 前言
- [1. 模板方法模式是什么?](#1. 模板方法模式是什么?)
- [2. 模板方法模式解决什么问题?](#2. 模板方法模式解决什么问题?)
- [3. 实现步骤](#3. 实现步骤)
- [4. 静态结构示例](#4. 静态结构示例)
-
- [4.1 抽象模板类:定义骨架流程](#4.1 抽象模板类:定义骨架流程)
- [4.2 子类:实现可变步骤](#4.2 子类:实现可变步骤)
- [4.3 客户端:只调用模板方法](#4.3 客户端:只调用模板方法)
- [5. 动态结构示例("流程骨架 + 运行时替换点")](#5. 动态结构示例(“流程骨架 + 运行时替换点”))
- [6. 钩子方法(Hook)](#6. 钩子方法(Hook))
- [7. 和代理模式对比](#7. 和代理模式对比)
-
- [7.1 结构相似,意图不同](#7.1 结构相似,意图不同)
- [7.2 关键差异](#7.2 关键差异)
- [8. 优缺点](#8. 优缺点)
-
- [8.1 优点](#8.1 优点)
- [8.2 缺点](#8.2 缺点)
- [9. 总结](#9. 总结)
前言
在面向对象设计里,有一种"访问前后要做事 "的需求。但代理模式(Proxy)更侧重于"替你去访问并控制访问 "。而模板方法模式(Template Method Pattern) 更关心的是:
一套固定流程(骨架算法)怎么写才能复用?哪些步骤允许子类替换?
它用来解决"流程很固定、但流程中的某些步骤可能变化"的问题。

1. 模板方法模式是什么?
模板方法模式:在抽象类中定义一个算法的骨架(模板方法),把通用步骤放在父类里,而把一些可变步骤延迟到子类中实现。
核心思想可以概括为:
- 父类:定义"流程怎么走"(固定不变的骨架)
- 子类:实现"流程里的可变步骤"(不同实现)
- 客户端:通常只调用父类提供的模板方法,流程由它驱动
GoF 经典结构:
- AbstractClass(抽象类/模板类):定义模板方法 + 通用逻辑
- ConcreteClass(具体子类):实现抽象步骤/覆盖钩子步骤
2. 模板方法模式解决什么问题?
常见场景就是:流程固定,但步骤可变。
例如:
-
数据处理流水线
- 校验 -> 转换 -> 入库
转换规则因类型不同而不同。
- 校验 -> 转换 -> 入库
-
算法流程复用
- 例如某类"关卡通关流程"
- 判断条件/奖励发放在不同关卡不同。
-
游戏/业务的步骤化执行
- 登录校验 -> 扣费 -> 下单
扣费策略可能不同。
- 登录校验 -> 扣费 -> 下单
-
框架级流程(半成品流程)
- 例如某些框架的"执行器":通用执行流程固定,可扩展点交给你填。
3. 实现步骤
写模板方法模式通常分为这几步:
-
抽象类里写模板方法(固定流程)
使用
final(强烈建议)让流程骨架不被子类篡改。 -
把可变步骤声明为抽象方法或可覆盖方法
- 抽象方法:子类必须实现
- 钩子方法(Hook):提供默认实现或让子类可选择覆盖
-
子类只关心"自己要替换的步骤"
- 不需要关心流程拼装顺序
-
客户端调用模板方法
- 由父类驱动整个流程
4. 静态结构示例
下面用一个"做饭流程"的例子来体现:步骤顺序固定,但切菜/烹饪方式可变。
4.1 抽象模板类:定义骨架流程
java
public abstract class CookTemplate {
// 模板方法:流程骨架(固定)
public final void cook() {
boilWater(); // 固定步骤1
prepare(); // 可变步骤2(交给子类)
cookFood(); // 可变步骤3(交给子类)
plate(); // 固定步骤4
}
// 固定步骤:父类直接实现
private void boilWater() {
System.out.println("煮水中...");
}
// 可变步骤:交给子类实现(抽象方法)
protected abstract void prepare();
// 可变步骤:交给子类实现(抽象方法)
protected abstract void cookFood();
// 固定步骤
private void plate() {
System.out.println("装盘完成!");
}
}
4.2 子类:实现可变步骤
java
public class NoodleCook extends CookTemplate {
@Override
protected void prepare() {
System.out.println("准备面条和调料");
}
@Override
protected void cookFood() {
System.out.println("煮面并调味");
}
}
public class RiceCook extends CookTemplate {
@Override
protected void prepare() {
System.out.println("准备米饭和配料");
}
@Override
protected void cookFood() {
System.out.println("蒸饭并加入配料");
}
}
4.3 客户端:只调用模板方法
java
public class Client {
public static void main(String[] args) {
CookTemplate cook1 = new NoodleCook();
cook1.cook();
CookTemplate cook2 = new RiceCook();
cook2.cook();
}
}
输出效果(顺序一致,差异在子类步骤):
- 煮水中...
- 准备面条和调料 / 准备米饭和配料
- 煮面并调味 / 蒸饭并加入配料
- 装盘完成!
5. 动态结构示例("流程骨架 + 运行时替换点")
你可以理解为模板方法模式的"动态性"来自两点:
cook()是固定骨架(父类不变)prepare()/cookFood()在运行时由子类决定
也就是说:流程模板固定,但"插入点"是多态的。
6. 钩子方法(Hook)
有时某个步骤不是"必须替换",而是"可选增强/可决定跳过"。
例如:决定是否执行某个动作:
java
public abstract class PaymentTemplate {
public final void pay() {
verify(); // 固定
if (shouldDiscount()) { // 钩子:默认 false,允许子类覆盖
discount();
}
charge(); // 固定
notifyUser(); // 固定
}
protected void verify() { System.out.println("校验订单"); }
protected boolean shouldDiscount() { return false; } // 钩子(默认不打折)
protected void discount() { System.out.println("执行打折"); }
protected void charge() { System.out.println("扣款"); }
protected void notifyUser() { System.out.println("通知用户"); }
}
public class VipPaymentTemplate extends PaymentTemplate {
@Override
protected boolean shouldDiscount() {
return true; // VIP 开启打折
}
}
7. 和代理模式对比
7.1 结构相似,意图不同
- 代理模式 :你不直接调用真实对象,而是"通过一个代表去控制访问并增强"
- 模板方法 :流程由模板类驱动,"步骤由子类替换",强调"算法骨架复用"
7.2 关键差异
-
谁控制顺序?
- 代理模式:通常是
Proxy在方法里"调用前后增强",但顺序不一定是"算法骨架固定" - 模板方法:父类明确规定"流程顺序 = 模板方法骨架",子类只填空
- 代理模式:通常是
-
扩展点是什么?
- 代理模式:扩展点是"访问前/访问后拦截逻辑"(权限、日志、缓存...)
- 模板方法:扩展点是"流程中的某些步骤实现"(准备/执行/收尾等)
-
复用的是什么?
- 代理模式:复用的是"控制访问 + 增强"的包装能力
- 模板方法:复用的是"一整套固定流程(算法骨架)"
8. 优缺点
8.1 优点
- 复用流程骨架:避免多个子类重复写相同的流程顺序
- 提高扩展性:子类只改可变步骤,符合开闭原则
- 降低出错概率 :模板方法可用
final固定骨架,减少流程被改坏的风险
8.2 缺点
- 类层级容易膨胀:可变步骤很多时,子类数量可能变多
- 不适合流程差异很大的情况:如果每个实现连顺序都差很多,就不适合模板方法
- 强耦合继承结构:模板方法主要依赖继承;如果你不想用继承,可以考虑策略模式等
9. 总结
模板方法模式的精髓是:
把一个算法/流程的"骨架"固定在父类里,把某些可变步骤交给子类实现。
这样既能复用整体流程,又能灵活替换其中的实现点。