# C++设计模式:模板方法模式——在基类中固定算法骨架,让子类填充可变步骤 > **引言**:当"流程固定,实现差异"开始频繁出现 很多团队第一次真正需要模板方法模式,不是在设计框架,而是在维护一套越来越相似的流程代码。 假设你在做一个数据处理系统,不同数据源的处理流程大致相同: ```cpp // CSV 文件处理 void processCSV(const std::string& path) { std::cout << "1. 读取文件: " << path << std::endl; std::vector data = readFile(path); std::cout << "2. 解析 CSV 格式..." << std::endl; auto parsed = parseCSV(data); std::cout << "3. 验证数据..." << std::endl; validate(parsed); std::cout << "4. 转换数据..." << std::endl; auto transformed = transformCSV(parsed); std::cout << "5. 保存结果..." << std::endl; save(transformed, "output.csv"); } // JSON 文件处理 void processJSON(const std::string& path) { std::cout << "1. 读取文件: " << path << std::endl; std::vector data = readFile(path); std::cout << "2. 解析 JSON 格式..." << std::endl; auto parsed = parseJSON(data); std::cout << "3. 验证数据..." << std::endl; validate(parsed); std::cout << "4. 转换数据..." << std::endl; auto transformed = transformJSON(parsed); std::cout << "5. 保存结果..." << std::endl; save(transformed, "output.json"); } // XML 文件处理 void processXML(const std::string& path) { // ... 几乎一样的流程 ... } ``` 刚开始看起来还好,但很快问题就出现了。 **问题 1:流程代码重复** **问题 2:步骤顺序可能被错误修改** **问题 3:新增步骤影响所有函数** **问题 4:无法保证算法结构被遵守** 这些问题的本质是:**流程骨架(算法结构)和具体实现(算法步骤)混在一起了。** 如果没有把"流程结构"单独建模,就很难保证: - • 流程步骤的顺序不被破坏 - • 流程的完整性不被违反 - • 新增公共步骤能统一添加 模板方法模式就是为解决这个问题而生的。它把**算法骨架**和**可变步骤**分离: - • 算法骨架在基类中定义(模板方法) - • 可变步骤由子类实现(原语操作) ------ ## 模板方法模式详解 ### 模式定义 > **模板方法模式(Template Method Pattern)**:**在一个方法中定义算法的骨架,将某些步骤延迟到子类实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。** 模板方法模式的核心思想是:**把"做什么"和"怎么做"分开**: - • **做什么**(算法骨架)由基类定义,决定步骤的顺序和必需性 - • **怎么做**(具体实现)由子类提供,决定每个步骤的具体行为 ### 核心思想 模板方法模式体现了几个重要的设计思想: **1. Hollywood 原则** ```cpp 传统思路:子类调用基类方法 class CSVProcessor { void process() { read(); // 子类主动调用 parse(); // 子类主动调用 validate(); // 子类主动调用 save(); // 子类主动调用 } }; 模板方法模式:基类调用子类方法 class DataProcessor { void process() { // 模板方法 read(); // 基类调用固定步骤 parse(); // 基类调用子类实现的步骤 validate(); // 基类调用固定步骤 save(); // 基类调用固定步骤 } }; ``` "不要打电话给我们,我们会打电话给你"——这是模板方法模式的哲学。基类(框架)说:"我知道整个流程,我会按顺序调用你们的方法,你们准备好就行。" **2. 算法结构稳定性** ``` 固定不变的部分: 步骤顺序 → 1→2→3→4→5 必需步骤 → 某些步骤必须执行 可选步骤 → 某些步骤可以跳过 ``` **3. 复用优于重复** 把公共流程提取到基类,避免在多个子类中重复。 ### 核心角色 模板方法模式包含两个核心角色: 1. **抽象类(AbstractClass)**: - • 定义模板方法(算法骨架) - • 实现固定步骤(公共行为) - • 声明原语操作(抽象方法,由子类实现) - • 可选地定义钩子函数(子类可重写) 2. **具体类(ConcreteClass)**: - • 实现原语操作(可变步骤) - • 可选重写钩子函数 可以把它理解成这样: - • **抽象类**是"流程经理",规定好要做哪些事、按什么顺序做 - • **具体类**是"执行者",完成每个步骤的具体工作 ### 基本实现(经典版本) 先看一个最经典的"数据处理器"例子。 ```cpp #include #include #include #include // 抽象基类:定义算法骨架 class DataProcessor { public: // 模板方法:定义算法结构(final防止子类重写) void processData() { std::cout << "=== 开始数据处理 ===" << std::endl; readFile(); // 步骤1:读取文件(固定步骤) parseData(); // 步骤2:解析数据(可变步骤,多态) validateData(); // 步骤3:验证数据(固定步骤) transformData(); // 步骤4:转换数据(可变步骤,多态) saveData(); // 步骤5:保存数据(固定步骤) logResult(); // 步骤6:记录结果(固定步骤) std::cout << "=== 数据处理完成 ===" << std::endl; } virtual ~DataProcessor() = default; protected: // ===== 固定步骤:由基类实现 ===== void readFile() { std::cout << "步骤1: 从数据源读取文件..." << std::endl; } void validateData() { std::cout << "步骤3: 验证数据格式和完整性..." << std::endl; } void saveData() { std::cout << "步骤5: 保存处理结果到目标..." << std::endl; } void logResult() { std::cout << "步骤6: 记录处理日志..." << std::endl; } // ===== 可变步骤:由子类实现(纯虚函数)===== virtual void parseData() = 0; virtual void transformData() = 0; }; // 具体子类1:CSV 数据处理器 class CSVProcessor : public DataProcessor { protected: void parseData() override { std::cout << "步骤2: 解析 CSV 格式(逗号分隔,处理引号)..." << std::endl; } void transformData() override { std::cout << "步骤4: 转换 CSV 数据(过滤空行、类型转换)..." << std::endl; } }; // 具体子类2:JSON 数据处理器 class JSONProcessor : public DataProcessor { protected: void parseData() override { std::cout << "步骤2: 解析 JSON 格式(花括号嵌套,键值对)..." << std::endl; } void transformData() override { std::cout << "步骤4: 转换 JSON 数据(扁平化、类型推断)..." << std::endl; } }; // 具体子类3:XML 数据处理器 class XMLProcessor : public DataProcessor { protected: void parseData() override { std::cout << "步骤2: 解析 XML 格式(标签匹配、属性解析)..." << std::endl; } void transformData() override { std::cout << "步骤4: 转换 XML 数据(XSLT 转换、命名空间)..." << std::endl; } }; int main() { std::unique_ptr csv = std::make_unique(); std::unique_ptr json = std::make_unique(); std::unique_ptr xml = std::make_unique(); csv->processData(); std::cout << std::endl; json->processData(); std::cout << std::endl; xml->processData(); return 0; } ``` 这个实现体现了模板方法模式最重要的特点: 1. **算法结构被固定**:1→2→3→4→5→6 的顺序不可改变 2. **公共步骤只实现一次**:readFile、validateData、saveData、logResult 不需要在每个子类中重复 3. **可变步骤由子类实现**:parseData 和 transformData 由各子类提供具体实现 4. **子类不能改变算法结构**:processData() 被声明为 final,子类无法重写来改变流程 ------ ## 虚函数与钩子函数 模板方法模式中有两类"可变点": ### 虚函数:必须实现的步骤 虚函数(尤其是纯虚函数)定义了算法中**必须由子类提供实现**的步骤: ```cpp class AbstractTemplate { public: void templateMethod() { step1(); // 调用纯虚函数,子类必须实现 step2(); // 调用纯虚函数,子类必须实现 step3(); // 调用纯虚函数,子类必须实现 } protected: virtual void step1() = 0; // 纯虚函数:子类必须实现 virtual void step2() = 0; // 纯虚函数:子类必须实现 virtual void step3() = 0; // 纯虚函数:子类必须实现 }; ``` **特点**: - • 子类**必须实现**这些方法,否则无法实例化 - • 这些步骤是算法的**必需部分** - • 编译器会在编译期检查 ### 钩子函数:可选重写的扩展点 钩子函数(Hook)提供了**可选的扩展点**,子类可以选择性地重写: ```cpp #include class GameAI { public: void takeTurn() { assessSituation(); // 固定步骤 if (shouldAttack()) { // 钩子函数:子类可以改变行为 executeAttack(); } else { defend(); } endTurn(); } virtual ~GameAI() = default; protected: void assessSituation() { std::cout << "评估战场态势..." << std::endl; } void executeAttack() { std::cout << "执行攻击策略!" << std::endl; } void defend() { std::cout << "采取防御姿态。" << std::endl; } void endTurn() { std::cout << "回合结束。" << std::endl; } // 钩子函数:提供默认实现,子类可选重写 virtual bool shouldAttack() { return false; // 默认策略:不攻击 } }; class AggressiveAI : public GameAI { protected: bool shouldAttack() override { std::cout << "激进AI:主动攻击!" << std::endl; return true; // 重写:总是攻击 } }; class DefensiveAI : public GameAI { protected: // 使用默认的 shouldAttack() 返回 false }; ``` **钩子函数的常见用途**: #### 1. 条件执行控制 #### 2. 扩展点插入 #### 3. 事件通知 ------ ## 关键技术专题:Hollywood 原则与算法骨架设计 ### Hollywood 原则详解 > **"Don't Call Us, We'll Call You"(不要打电话给我们,我们会打电话给你)** 这是模板方法模式的核心哲学。传统继承是"子类主动调用基类方法",而模板方法模式是"基类主动调用子类方法"。 ```cpp // ❌ 传统继承:子类主动调用 class Child : public Parent { public: void doWork() { Parent::step1(); // 子类主动调用基类方法 Parent::step2(); step3(); // 子类自己的方法 Parent::step4(); } }; // ✅ 模板方法模式:基类主动调用 class Parent { public: void doWork() { // 模板方法:基类定义流程 step1(); // 基类调用固定步骤 step2(); // 基类调用固定步骤 doStep3(); // 基类调用子类实现的步骤 step4(); // 基类调用固定步骤 } protected: void step1() { /* 固定实现 */ } void step2() { /* 固定实现 */ } virtual void doStep3() = 0; // 纯虚函数,子类实现 void step4() { /* 固定实现 */ } }; ``` ### 算法骨架设计原则 #### 1. 步骤顺序不可变 ```cpp class ReportGenerator { public: void generateReport() { // 固定的5步流程 collectData(); // 顺序1:收集数据 analyzeData(); // 顺序2:分析数据 createCharts(); // 顺序3:创建图表 writeSummary(); // 顺序4:写入摘要 exportReport(); // 顺序5:导出报告 } // 子类不能改变这个顺序 }; ``` #### 2. 步骤粒度合理 ```cpp // ✅ 好的设计:每个步骤职责单一 class TextProcessor { public: void process() { readInput(); // 职责:IO读取 tokenize(); // 职责:词法分析 parse(); // 职责:语法分析 optimize(); // 职责:优化 generateOutput(); // 职责:代码生成 } }; // ❌ 差的设计:步骤过于粗糙 class TextProcessor { public: void process() { doEverything(); // 难以定制和复用 } }; ``` #### 3. 区分稳定步骤和可变步骤 ```cpp class DataPipeline { public: void execute() { authenticate(); // 稳定步骤:不太可能变化 connect(); // 稳定步骤:不太可能变化 processData(); // 可变步骤:不同数据源处理方式差异大 log(); // 稳定步骤:不太可能变化 } protected: void authenticate() { /* ... */ } void connect() { /* ... */ } void log() { /* ... */ } virtual void processData() = 0; // 核心变化点 }; ``` ------ ## 现代 C++ 实现 ### 使用 final 保护模板方法 在现代 C++ 中,可以使用 `final` 关键字防止子类意外重写模板方法: ```cpp class DataProcessor { public: void processData() final { // final:子类不能重写这个方法 readFile(); parseData(); validateData(); transformData(); saveData(); } // ... }; ``` `final` 的好处: - • 编译期检查,防止子类意外破坏算法结构 - • 明确表达"这个方法不应该被修改"的意图 - • 可能帮助编译器优化(去虚化) ------ **一句话记忆**: - • **模板方法是"在同一流程中,某些步骤可变";策略是"整个算法可以替换"。** - • **模板方法是"不同子类有相同流程,某些步骤实现不同";状态是"同一对象在不同状态下行为不同"。** - • **模板方法是"基类定义流程,调用子类方法";命令模式是"把请求封装成对象,支持排队和撤销"。** ------ ## 写在最后 模板方法模式是行为型模式的收官之作,它解决的是"流程固定、实现差异"的经典问题。在现代 C++ 和框架设计中,模板方法模式无处不在。 ### 核心价值 1. **流程控制**:基类保证算法结构的稳定性 2. **代码复用**:公共步骤只在基类实现一次 3. **扩展点明确**:通过虚函数和钩子函数提供扩展能力 4. **结构清晰**:算法骨架和具体实现分离,职责明确 ### 与其他模式的关系 从系列位置来看: - • **策略模式**封装可替换算法 - • **命令模式**封装请求支持撤销 - • **状态模式**封装状态驱动的行为变化 - • **模板方法模式**固定算法骨架 这四种模式构成了行为型模式的完整图景:**算法变化、请求封装、状态变化、流程控制**。 **记住这三句话**: 1.**模板方法模式的本质是"在基类中定义流程,调用子类方法"。** 2.**当多个子类遵循相同流程、只有实现差异时,模板方法模式是最自然的选择。** 3.**模板方法模式是框架设计的基石——几乎所有成熟框架都大量使用这个模式。**