🎯 导读:本文讲解模板方法模式,帮助你理解如何用”好莱坞原则”来控制算法的执行流程,把不变的步骤锁死,把变化的步骤留给子类填空。
📋 模板方法模式(Template Method Pattern) 是行为型设计模式中最基础、也最能体现面向对象精髓的一个模式。
如果说”工厂模式”是在管理对象的创建,那么”模板方法模式”就是在管理算法的执行流程。
它的核心思想可以用一句话概括:**”制定一套标准操作流程(SOP),把固定不变的步骤自己写好,把需要灵活变化的步骤留给子类去填空。”**
在软件工程中,这被称为”好莱坞原则”(Hollywood Principle):**”不要给我们打电话,我们会给你打电话。”** 意思是说,子类不需要主动去控制流程,只需要把填空题做好,父类会在恰当的时机自动调用子类的代码。
📚 1. 客户端(使用者)需要知道哪些类?
在使用模板方法模式时:
✅ 你需要知道 2 个类:
| 序号 | 类名 | 说明 |
|---|---|---|
| 1️⃣ | 抽象基类Abstract Class |
你必须知道它,因为最核心的”启动流程”的方法定义在这里 |
| 2️⃣ | 具体子类Concrete Class |
你需要知道你要实例化哪种具体的流程配置 |
❌ 你不需要/绝不应该知道的:
- 步骤方法的调用顺序: 客户端只需要调用一个”一键启动”的方法,具体内部先做什么后做什么,完全由父类控制 🔒
- 哪些步骤是可变的: 这是子类实现者的职责,使用者不需要关心
🎬 2. 生动的实战场景:FITK 的有限元分析(FEA)标准求解工作流
在你的 FITK 拓扑优化软件中,每次优化迭代都需要进行一次完整的有限元求解(FEA)。不管是做简单的”线性静力学分析”,还是做复杂的”非线性动力学分析”,整个大流程是绝对固定的,不能错乱。
📋 固定的标准流程(SOP):
| 步骤 | 操作 | 是否变化 |
|---|---|---|
| 1️⃣ | 导入网格模型 | 🔒 固定:读取文件的代码是一样的 |
| 2️⃣ | 组装全局刚度矩阵 | 🔓 变化:静力学和动力学的公式完全不同 |
| 3️⃣ | 求解线性方程组 | 🔓 变化:可能用不同的求解器,如 PCG 或直接法 |
| 4️⃣ | 导出 VTK 结果文件 | 🔒 固定:写文件的格式是一样的 |
💀 痛点
如果不控制流程,初级程序员可能会在代码里先”求解方程组”,再”组装矩阵”,导致程序直接崩溃。
✅ 解法(模板方法模式)
在父类中写一个死死锁住的 runSimulation() 函数规定死 1→2→3→4 的顺序。把 2 和 3 设为纯虚函数,逼着子类去实现。
📊 UML 类图
classDiagram
class BaseFEASolver {
+runSimulation()
+loadMesh()
+assembleMatrix()
+solveEquations()
+exportVTK()
}
class LinearStaticSolver {
+assembleMatrix()
+solveEquations()
}
class NonlinearDynamicSolver {
+assembleMatrix()
+solveEquations()
}
LinearStaticSolver --|> BaseFEASolver : 继承
NonlinearDynamicSolver --|> BaseFEASolver : 继承
📌 核心设计:
runSimulation()是final的,防止子类篡改流程assembleMatrix()和solveEquations()是纯虚函数,强制子类实现loadMesh()和exportVTK()是固定步骤,父类已实现
💻 3. C++ 现代代码生动演绎
在 C++ 中实现模板方法模式,有两个极其重要的关键字:protected(保护子类专属步骤不被外部乱调)和 final(C++11 引入,防止子类篡改核心流程)。
1 |
|
🔧 4. C++ 架构设计上的精妙之处
在上面这段代码中,有几个 C++ 架构设计的细节值得反复品味:
🎯 设计要点一:public vs protected
| 方法 | 访问级别 | 设计意图 |
|---|---|---|
runSimulation() |
public |
客户端唯一能看到的入口,一键启动 |
loadMesh() / exportVTK() |
protected |
固定步骤,子类可见,外部不可见 |
assembleMatrix() / solveEquations() |
protected + 纯虚 |
强制子类实现,但外部绝不能直接调用 |
💡 好处:客户端在拿到
solver1指针时,按代码提示只能看到runSimulation()这一个方法。客户端绝对不可能发生”还没组装矩阵就去调求解方程组”的弱智错误。
🔒 设计要点二:final 关键字的威力
| 情况 | 后果 |
|---|---|
❌ 不加 final |
某个胆大包天的初级程序员可能会在 LinearStaticSolver 里重写 runSimulation(),把导出 VTK 的步骤给删了,破坏整个软件的数据标准 |
✅ 加上 final |
编译器会直接报错,死死捍卫住架构师定下的标准流程 |
⚖️ 5. 模板方法模式的优缺点
✅ 优点:
| 优点 | 说明 |
|---|---|
| 🔄 代码复用 | 把不变的行为搬到超类,去除了子类中的重复代码 |
| 🛡️ 流程控制 | 父类控制算法流程,防止子类乱改顺序 |
| 🔓 扩展灵活 | 子类可以重新定义某些步骤,而不改变算法结构 |
| 🎯 遵守 OCP | 新增子类不需要修改父类代码 |
❌ 缺点:
| 缺点 | 说明 |
|---|---|
| 😵 增加复杂性 | 每个不同的实现都要新增一个子类 |
| 🔗 继承限制 | 继承是静态的,运行时无法切换算法 |
🎯 6. 总结:什么时候用模板方法模式?
🟢 适用场景:
- 有多个类逻辑结构相同,只是某些具体步骤不同
- 需要控制算法的执行流程,防止子类乱来
- 想让子类只关注自己那部分逻辑,不用管整体流程
🔵 与其他模式的对比:
| 模式 | 核心作用 |
|---|---|
| 📋 模板方法 | 控制算法执行流程(行为型) |
| 🏭 工厂方法 | 控制对象创建流程(创建型) |
| 🔗 策略模式 | 可以在运行时切换整个算法 |
💡 一句话总结:模板方法模式通过把不变的行为搬到超类,去除了子类中的重复代码。它不仅提供了代码复用的手段,更是控制复杂系统执行流程的终极武器!
- 本文作者: 迪丽惹Bug
- 本文链接: https://lyroom.github.io/2026/06/30/模板方法模式详解/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!