模板方法模式是面向对象设计模式,基于虚函数和继承实现算法骨架与可变步骤分离;父类定义final模板方法,子类重写protected虚函数钩子,与C++模板无关。

模板方法模式的核心不是C++模板
很多人看到“模板方法”就下意识写 template<typename t></typename>,这是典型误解。模板方法模式(Template Method Pattern)是面向对象设计模式,和C++的泛型模板(template)完全无关——它靠的是虚函数和继承,不是编译期类型推导。
关键在于:父类定义算法骨架(即“模板方法”),把可变步骤声明为 virtual 或 pure virtual,子类只重写这些钩子函数,不改变流程顺序。
用虚函数实现标准模板方法结构
一个典型的实现包含三部分:一个 final 的公有成员函数(模板方法)、若干受保护的虚函数(钩子)、以及可能的默认实现。
-
execute()必须是final,防止子类意外重写流程逻辑 - 钩子函数如
setup()、doWork()、teardown()应为protected virtual,子类按需重写 - 如果某步必须由子类实现,就用
= 0声明纯虚函数;若提供默认行为,直接给出函数体 - 避免在构造函数里调用虚函数——此时虚表还没完全初始化,
doWork()会静态绑定到基类版本
class AbstractTask {
public:
void execute() final { // 模板方法:不可被重写
setup();
doWork();
teardown();
}
protected:
virtual void setup() { /* 默认空实现 */ }
virtual void doWork() = 0; // 必须重写
virtual void teardown() { /* 默认空实现 */ }
};
为什么不用 std::function 替代虚函数?
有人想用 std::function 成员变量 + 构造时注入来模拟钩子,这会破坏模式本质:它让算法骨架和具体行为耦合在实例创建时,丧失了多态调度的清晰边界,也失去编译期检查(比如漏传某个 std::function 就运行时报错)。
更实际的问题是:
- 每个子类对象都额外携带几个
std::function对象(通常至少 24 字节),而虚函数调用零额外存储 - 无法通过
dynamic_cast或typeid判断具体策略类型,调试和日志困难 - 违反开闭原则——新增子类要改工厂或构造逻辑,而非单纯新增一个派生类
容易忽略的生命周期与 const 正确性
模板方法常用于资源管理(如文件处理、网络请求),这时 setup() 和 teardown() 往往涉及 RAII。但要注意:
- 如果模板方法本身声明为
const(如void execute() const),那所有钩子也必须是const,导致无法修改成员状态——绝大多数场景下不该加const - 钩子函数内部抛异常时,
teardown()可能不会执行,需考虑用 RAII 封装资源,而不是依赖模板方法的 finally 风格保证 - 若子类需要访问自身特有成员,钩子函数应设计为 protected,而非 private —— 否则子类无法在
doWork()中使用自己的数据
真正难的不是写出结构,而是判断哪些步骤该抽象成钩子、哪些该固化在模板方法里——这取决于业务变化频率,不是语法问题。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











