模板方法模式(Template Method)是行为型设计模式的一种,它定义了一个算法的骨架,将算法的一些步骤延迟到子类中实现。这种模式允许子类在不改变算法结构的情况下,重新定义算法中的某些步骤,从而实现算法的复用与定制。
一、核心思想与角色
模板方法模式的核心是“固定流程,可变步骤”,通过在父类中定义算法的框架,将可变部分委托给子类实现。其核心角色如下:
| 角色名称 | 核心职责 |
|---|---|
| 抽象类(AbstractClass) | 定义算法的骨架(模板方法),包含多个抽象方法(子类必须实现的步骤)和可选的钩子方法(子类可重写的步骤)。 |
| 具体子类(ConcreteClass) | 实现抽象类中的抽象方法,完成算法中与具体子类相关的步骤,可选择性重写钩子方法。 |
核心思想:父类封装不变的算法结构,子类负责实现具体的可变步骤,既保证了算法结构的一致性,又允许子类灵活定制部分步骤。
二、实现示例(数据处理流程)
假设我们需要设计一个数据处理框架,流程包括:数据读取、数据清洗、数据处理、结果保存四个步骤。其中,数据读取和结果保存的方式可能因数据来源(文件、数据库)不同而变化,但数据清洗和处理的核心逻辑固定。使用模板方法模式可复用核心流程,同时允许定制读取和保存步骤:
1#include <iostream> 2#include <string> 3#include <vector> 4 5// 1. 抽象类:数据处理器(定义算法骨架) 6class DataProcessor { 7public: 8 // 模板方法:定义算法的骨架(不可被子类重写) 9 void process() final { 10 readData(); // 步骤1:读取数据 11 cleanData(); // 步骤2:清洗数据(固定实现) 12 analyzeData(); // 步骤3:分析数据(固定实现) 13 if (needSaveResult()) { // 钩子方法:判断是否需要保存 14 saveResult(); // 步骤4:保存结果 15 } 16 postProcess(); // 钩子方法:后续处理(可选) 17 } 18 19 // 抽象方法:读取数据(子类必须实现) 20 virtual void readData() = 0; 21 22 // 抽象方法:保存结果(子类必须实现) 23 virtual void saveResult() = 0; 24 25 // 钩子方法1:是否需要保存结果(默认需要) 26 virtual bool needSaveResult() { 27 return true; 28 } 29 30 // 钩子方法2:后续处理(默认空实现) 31 virtual void postProcess() {} 32 33 virtual ~DataProcessor() = default; 34 35protected: 36 // 受保护的方法:数据清洗(固定实现,子类可调用但不能重写) 37 void cleanData() { 38 std::cout << "执行数据清洗:去除空值和异常值" << std::endl; 39 } 40 41 // 受保护的方法:数据分析(固定实现) 42 void analyzeData() { 43 std::cout << "执行数据分析:计算均值和方差" << std::endl; 44 } 45 46 // 存储数据的容器(供子类使用) 47 std::vector<double> data; 48}; 49 50// 2. 具体子类1:文件数据处理器 51class FileDataProcessor : public DataProcessor { 52private: 53 std::string filename; 54 55public: 56 FileDataProcessor(const std::string& fn) : filename(fn) {} 57 58 // 实现读取数据:从文件读取 59 void readData() override { 60 std::cout << "从文件[" << filename << "]读取数据" << std::endl; 61 // 模拟读取数据 62 data = {1.2, 3.4, 5.6, 7.8}; 63 } 64 65 // 实现保存结果:保存到文件 66 void saveResult() override { 67 std::cout << "将结果保存到文件[" << filename << ".result]" << std::endl; 68 } 69 70 // 重写钩子方法:添加自定义后续处理 71 void postProcess() override { 72 std::cout << "文件处理器后续处理:生成数据可视化图表" << std::endl; 73 } 74}; 75 76// 2. 具体子类2:数据库数据处理器 77class DatabaseDataProcessor : public DataProcessor { 78private: 79 std::string dbConnection; 80 81public: 82 DatabaseDataProcessor(const std::string& conn) : dbConnection(conn) {} 83 84 // 实现读取数据:从数据库读取 85 void readData() override { 86 std::cout << "从数据库[" << dbConnection << "]读取数据" << std::endl; 87 // 模拟读取数据 88 data = {2.3, 4.5, 6.7, 8.9}; 89 } 90 91 // 实现保存结果:保存到数据库 92 void saveResult() override { 93 std::cout << "将结果保存到数据库[" << dbConnection << "]" << std::endl; 94 } 95 96 // 重写钩子方法:不需要保存结果 97 bool needSaveResult() override { 98 return false; // 数据库数据可直接使用,无需额外保存 99 } 100}; 101 102// 客户端代码:使用数据处理框架 103int main() { 104 // 处理文件数据 105 std::cout << "=== 处理文件数据 ===" << std::endl; 106 DataProcessor* fileProcessor = new FileDataProcessor("data.txt"); 107 fileProcessor->process(); // 调用模板方法,执行完整流程 108 109 // 处理数据库数据 110 std::cout << "\n=== 处理数据库数据 ===" << std::endl; 111 DataProcessor* dbProcessor = new DatabaseDataProcessor("mysql://localhost:3306/data_db"); 112 dbProcessor->process(); 113 114 // 释放资源 115 delete dbProcessor; 116 delete fileProcessor; 117 118 return 0; 119} 120 121
三、代码解析
- 抽象类(DataProcessor):
- 模板方法(process()):用
final修饰确保子类不能重写,定义了数据处理的完整流程:readData() → cleanData() → analyzeData() → saveResult() → postProcess()。 - 抽象方法:
readData()和saveResult()声明为纯虚方法,要求子类必须实现(因不同数据源的读取和保存方式不同)。 - 固定方法:
cleanData()和analyzeData()为protected的具体实现,封装了所有子类共享的核心逻辑,子类不能重写但可调用。 - 钩子方法:
needSaveResult()(默认返回true)和postProcess()(默认空实现)允许子类选择性重写,用于定制流程中的可选步骤。
- 模板方法(process()):用
- 具体子类:
FileDataProcessor:实现从文件读取和保存数据,重写postProcess()添加可视化处理。DatabaseDataProcessor:实现从数据库读取数据,重写needSaveResult()返回false(无需额外保存结果)。
两个子类都复用了process()定义的流程,仅定制了需要变化的步骤。
- 客户端使用:
客户端通过抽象类接口创建具体处理器并调用process(),无需关心流程细节,只需确保子类正确实现了抽象方法。
四、核心优势与适用场景
优势
- 代码复用:将算法中不变的部分集中在父类,避免子类重复实现,提高代码复用率。
- 结构统一:模板方法固定了算法结构,确保所有子类遵循相同的流程,维护一致性。
- 灵活定制:子类可通过重写抽象方法和钩子方法,定制算法中的可变步骤,符合开闭原则。
- 反向控制:父类控制流程,子类提供实现,体现“好莱坞原则”(“不要调用我们,我们会调用你”)。
适用场景
- 流程固定但步骤可变:如框架设计(请求处理、数据解析、工作流引擎)、生命周期管理(初始化→运行→销毁)。
- 多个子类共享核心逻辑:当多个子类有相同的步骤流程,仅部分步骤实现不同时。
- 需要控制子类扩展:通过模板方法限制子类只能扩展特定步骤,避免破坏算法结构。
五、与其他模式的区别
| 模式 | 核心差异点 |
|---|---|
| 模板方法 | 父类定义算法骨架,子类实现具体步骤,强调“固定流程+可变实现”。 |
| 策略模式 | 封装不同算法,客户端动态选择,算法间相互独立,无流程依赖。 |
| 工厂方法 | 父类定义创建对象的接口,子类决定具体创建哪种对象,专注于对象创建。 |
| 观察者模式 | 定义对象间的一对多依赖,强调状态变化通知,与算法流程无关。 |
六、实践建议
- 合理设计钩子方法:钩子方法应仅用于影响流程的可选步骤,避免过度使用导致流程混乱。
- 控制方法访问权限:固定步骤设为
private或protected final防止子类重写,抽象步骤设为protected pure virtual,确保子类实现。 - 避免模板方法膨胀:当算法步骤过多时,可拆分抽象类(如使用组合模式),避免单个类过于庞大。
- 文档化模板流程:清晰注释模板方法的执行顺序和各步骤的作用,便于子类实现和维护。
模板方法模式的核心价值在于“标准化流程,个性化实现”,它通过分离算法的固定结构与可变步骤,既保证了系统的一致性,又为定制化需求提供了灵活的扩展点。在框架设计、流程管理等场景中,模板方法模式是实现代码复用和结构统一的重要手段。