模板方法模式的核心是“骨架与细节分离”,它通过基类定义算法的固定流程,并将可变步骤推迟到子类实现。其关键在于使用抽象基类定义算法骨架,其中模板方法通常是非虚函数以防止被重写,而可变步骤则通过纯虚函数(强制子类实现)和虚函数(带默认实现,子类可选覆盖)来实现,同时非虚函数用于所有子类共享的固定步骤。此外,还引入钩子函数(hook method),为子类提供扩展点而不强制实现。例如在文档处理示例中,processdocument() 是模板方法,固定了“加载→解析→格式化→保存”的流程;loaddocument() 和 savedocument() 是非虚函数,表示通用步骤;parsecontent() 是纯虚函数,必须由子类实现;applyformatting() 是虚函数,带有默认实现;shouldlogprogress() 是钩子函数,允许子类选择性地启用某些行为。

C++模板方法模式的核心在于它允许你定义一个算法的骨架,但将其中一些步骤的实现推迟到子类。说白了,就是把一个大任务的固定流程定死,但把其中一些个性化的、可变的部分留给不同的实现者去完成。

要设计C++中的模板方法模式,你需要一个抽象基类来承载这个算法的骨架。这个基类会包含一个非虚的(或有时是虚的,但通常不推荐覆盖整个模板)“模板方法”,它按特定顺序调用一系列“基本操作”。这些基本操作有些是纯虚函数,强制子类去实现;有些是虚函数,带有默认实现,子类可以选择性地覆盖;还有些是具体的非虚函数,代表所有子类都共享的固定步骤。
例如,想象一个处理文档的流程:
立即学习“C++免费学习笔记(深入)”;

#include#include #include // 抽象基类:文档处理器 class DocumentProcessor { public: // 模板方法:定义处理文档的固定算法骨架 // 通常是非虚的,确保算法流程不被子类改变 void processDocument() { std::cout << "--- 开始处理文档 ---" << std::endl; // 固定步骤1:加载文档 loadDocument(); // 可变步骤1:解析内容(强制子类实现) parseContent(); // 可变步骤2:应用格式(子类可选实现或使用默认) applyFormatting(); // 固定步骤2:保存文档 saveDocument(); std::cout << "--- 文档处理完成 ---" << std::endl; } protected: // 基本操作1:加载文档 (固定步骤) void loadDocument() { std::cout << "通用操作: 正在加载文档..." << std::endl; // 实际加载逻辑... } // 基本操作2:解析内容 (纯虚函数,强制子类实现) virtual void parseContent() = 0; // 基本操作3:应用格式 (虚函数,带默认实现,子类可覆盖) virtual void applyFormatting() { std::cout << "通用操作: 正在应用默认格式..." << std::endl; } // 基本操作4:保存文档 (固定步骤) void saveDocument() { std::cout << "通用操作: 正在保存文档..." << std::endl; // 实际保存逻辑... } // 钩子函数 (Hook Method): 子类可以重写以在特定点插入代码,但不强制 virtual bool shouldLogProgress() const { return false; // 默认不记录进度 } }; // 具体子类:PDF文档处理器 class PdfDocumentProcessor : public DocumentProcessor { protected: void parseContent() override { std::cout << "PDF处理器: 正在解析PDF特定内容..." << std::endl; // PDF解析逻辑... } // 覆盖默认格式化,应用PDF特定格式 void applyFormatting() override { std::cout << "PDF处理器: 正在应用PDF排版格式..." << std::endl; } bool shouldLogProgress() const override { return true; // PDF处理器选择记录进度 } }; // 具体子类:文本文档处理器 class TextDocumentProcessor : public DocumentProcessor { protected: void parseContent() override { std::cout << "文本处理器: 正在解析纯文本内容..." << std::endl; // 文本解析逻辑... } // 不覆盖applyFormatting(), 使用基类的默认实现 }; /* // 示例用法 int main() { PdfDocumentProcessor pdfProcessor; pdfProcessor.processDocument(); std::cout << "\n"; TextDocumentProcessor textProcessor; textProcessor.processDocument(); return 0; } */
在这个例子里,processDocument() 就是模板方法,它固定了文档处理的流程:加载 -> 解析 -> 格式化 -> 保存。loadDocument() 和 saveDocument() 是所有子类都共享的固定步骤。parseContent() 是纯虚函数,强制子类必须提供自己的解析逻辑。而 applyFormatting() 是一个虚函数,提供了默认实现,子类可以选择是否覆盖。shouldLogProgress() 则是一个典型的钩子函数,子类可以决定是否启用某个可选行为。
模板方法模式的核心思想是什么?
模板方法模式的核心,我觉得,就是“骨架与细节分离”。它提供了一种机制,让你在基类中定义一个算法的步骤序列——也就是那个“骨架”——而将其中某些具体步骤的实现推迟到子类。这就像是定好了菜谱的主流程(比如先切菜,再炒,最后调味),但具体切什么菜、怎么炒、用什么调料,则由不同的厨师(子类)来发挥。

它特别强调一个原则,有时候被称为“好莱坞原则”:“别打电话给我们,我们会打电话给你。”这意味着,基类(算法的骨架)掌握着控制权,它在适当的时候会“调用”子类提供的具体实现。这保证了算法的整体结构稳定不变,同时又允许局部细节的灵活变化。这和策略模式有点不同,策略模式是客户端选择一个完整的算法,而模板方法模式是基类定义了一个算法的结构,然后让子类去填充其中的某些部分。可以说,模板方法模式更侧重于算法的“内聚性”和“流程控制”。
模板方法模式在C++中如何实现?有哪些关键点?
在C++里实现模板方法模式,主要依赖于面向对象的三大支柱之一:继承。
关键点包括:
- 抽象基类与继承: 你需要一个抽象基类(或至少是一个带有虚函数的基类)来定义模板方法和那些可变的基本操作。子类会继承这个基类,并实现或覆盖其中的虚函数。
-
虚函数与纯虚函数: 这是实现“可变步骤”的关键。
-
纯虚函数 (
= 0): 用于那些必须由子类提供的具体实现。如果一个基类包含纯虚函数,它就变成了抽象类,不能直接实例化,只能通过其具体子类来使用。这强制了子类必须实现这些步骤。 - 虚函数(带默认实现): 用于那些有通用默认行为,但子类也可以选择性地提供自己实现的步骤。这提供了灵活性,子类可以不关心这些步骤,也可以完全定制。
- 非虚函数: 用于那些在所有子类中都保持不变的固定步骤。
-
纯虚函数 (
- 模板方法的非虚性(通常): 模板方法本身(







