模板方法模式(Template Method):骨架父类定,步骤子类填
更新时间:2026-08-25。本文按"先看不用模式的困境 → 再用模式重构 → 类图对比分析"的结构,论述模板方法模式。可运行代码:demos/design-patterns/template-method。
一、问题场景:流程骨架相同,个别步骤不同
泡茶与泡咖啡(烧水→冲泡→加料→倒杯)、数据迁移(连接→抽取→转换→装载)、编译流程(预处理→编译→链接)。整体流程一致,中间步骤由具体场景决定。
二、不使用模式:每个实现复制一份完整流程
cpp
void makeTea() {
boilWater(); addTeaLeaf(); steep(); addHoney(); pourCup();
}
void makeCoffee() {
boilWater(); addCoffee(); brew(); addMilk(); pourCup();
}
// 流程顺序改了(如先加料后冲泡):两个函数都要改| 缺陷 | 说明 |
|---|---|
| 骨架重复 | 流程逻辑每处复制一份 |
| 流程变更不同步 | 顺序调整要改所有副本,容易漏 |
| 违反 DRY | 不变的部分反复重写 |
| 结构无约束 | 没人保证各实现的流程顺序一致 |
不使用模式的类图

三、使用模式:骨架收敛到父类模板方法,步骤是钩子
模板方法模式在抽象父类里定义 make()(模板方法):固定流程骨架,把可变步骤声明为虚函数(钩子);子类只覆写需要的步骤,骨架由父类保证不变。
cpp
// beverage.h
class Beverage { // 抽象父类
public:
void make() { // 模板方法:固定骨架
boilWater();
addMain(); // 钩子:子类实现
steep();
addExtra(); // 钩子
pourCup();
}
virtual ~Beverage() = default;
protected:
virtual void addMain() = 0; // 必须覆写
virtual void addExtra() {} // 可选钩子(默认空)
private:
void boilWater() { /* 公共步骤 */ }
void steep() { /* 公共步骤 */ }
void pourCup() { /* 公共步骤 */ }
};
class Tea : public Beverage {
protected:
void addMain() override { /* 放茶叶 */ }
void addExtra() override { /* 加蜂蜜 */ }
};
class Coffee : public Beverage {
protected:
void addMain() override { /* 放咖啡粉 */ }
void addExtra() override { /* 加牛奶 */ }
};
// 使用:Tea t; t.make(); —— 骨架一致,步骤不同使用模式的类图

四、类图对比与分析
| 维度 | 不使用(复制骨架) | 使用模板方法 |
|---|---|---|
| 骨架 | 每实现复制一份 | 父类一份,全子类共享 |
| 流程变更 | 改所有副本 | 只改父类模板方法 |
| 步骤扩展 | 新实现重写全部 | 只覆写钩子 |
| 顺序保证 | 靠自觉 | 骨架强制(LSP:子类不破坏流程) |
| 结构成本 | 无 | 继承层级 + 虚函数调用 |
分析结论:模板方法用"继承 + 钩子"把"不变骨架"与"可变步骤"分离——骨架逻辑只在父类出现一次,子类只填差异。它是"继承复用"最直接的形态,是 OCP 在流程维度上的体现。注意与策略的区别:模板方法用继承在类层级固定流程;策略用组合在运行时替换算法。
五、适用边界
| 适用 | 不适用 |
|---|---|
| 流程骨架稳定、步骤多变 | 整个流程都会变(该用策略) |
| 多个实现共享同一流程 | 只有一种实现(模板是多余抽象) |
| 希望强制统一流程顺序 | 子类需要改变骨架顺序(模板会限制) |
钩子设计建议:钩子分两种——抽象钩子(必须覆写,如 addMain())与可选钩子(默认空,如 addExtra());父类模板方法中可以插入 before/after 扩展点(如 validate())而不破坏既有子类。
一句话总结
模板方法模式把固定流程收敛到父类的模板方法中,把可变步骤留作子类覆写的钩子——骨架只写一份、顺序由父类强制、扩展只动钩子;它是流程类复用的首选,但整个流程都会变化时应改用策略模式。