C++设计模式之行为型模式:模板方法模式(Template Method)
模板方法模式(Template Method)是行为型设计模式的一种,它定义了一个算法的骨架,将算法的一些步骤延迟到子类中实现。这种模式允许子类在不改变算法结构的情况下,重新定义算法中的某些步骤,从而实现算法的复用与定制。
一、核心思想与角色
模板方法模式的核心是“固定流程,可变步骤”,通过在父类中定义算法的框架,将可变部分委托给子类实现。其核心角色如下:
角色名称 | 核心职责 |
---|---|
抽象类(AbstractClass) | 定义算法的骨架(模板方法),包含多个抽象方法(子类必须实现的步骤)和可选的钩子方法(子类可重写的步骤)。 |
具体子类(ConcreteClass) | 实现抽象类中的抽象方法,完成算法中与具体子类相关的步骤,可选择性重写钩子方法。 |
核心思想:父类封装不变的算法结构,子类负责实现具体的可变步骤,既保证了算法结构的一致性,又允许子类灵活定制部分步骤。
二、实现示例(数据处理流程)
假设我们需要设计一个数据处理框架,流程包括:数据读取、数据清洗、数据处理、结果保存四个步骤。其中,数据读取和结果保存的方式可能因数据来源(文件、数据库)不同而变化,但数据清洗和处理的核心逻辑固定。使用模板方法模式可复用核心流程,同时允许定制读取和保存步骤:
#include <iostream>
#include <string>
#include <vector>// 1. 抽象类:数据处理器(定义算法骨架)
class DataProcessor {
public:// 模板方法:定义算法的骨架(不可被子类重写)void process() final {readData(); // 步骤1:读取数据cleanData(); // 步骤2:清洗数据(固定实现)analyzeData(); // 步骤3:分析数据(固定实现)if (needSaveResult()) { // 钩子方法:判断是否需要保存saveResult(); // 步骤4:保存结果}postProcess(); // 钩子方法:后续处理(可选)}// 抽象方法:读取数据(子类必须实现)virtual void readData() = 0;// 抽象方法:保存结果(子类必须实现)virtual void saveResult() = 0;// 钩子方法1:是否需要保存结果(默认需要)virtual bool needSaveResult() {return true;}// 钩子方法2:后续处理(默认空实现)virtual void postProcess() {}virtual ~DataProcessor() = default;protected:// 受保护的方法:数据清洗(固定实现,子类可调用但不能重写)void cleanData() {std::cout << "执行数据清洗:去除空值和异常值" << std::endl;}// 受保护的方法:数据分析(固定实现)void analyzeData() {std::cout << "执行数据分析:计算均值和方差" << std::endl;}// 存储数据的容器(供子类使用)std::vector<double> data;
};// 2. 具体子类1:文件数据处理器
class FileDataProcessor : public DataProcessor {
private:std::string filename;public:FileDataProcessor(const std::string& fn) : filename(fn) {}// 实现读取数据:从文件读取void readData() override {std::cout << "从文件[" << filename << "]读取数据" << std::endl;// 模拟读取数据data = {1.2, 3.4, 5.6, 7.8};}// 实现保存结果:保存到文件void saveResult() override {std::cout << "将结果保存到文件[" << filename << ".result]" << std::endl;}// 重写钩子方法:添加自定义后续处理void postProcess() override {std::cout << "文件处理器后续处理:生成数据可视化图表" << std::endl;}
};// 2. 具体子类2:数据库数据处理器
class DatabaseDataProcessor : public DataProcessor {
private:std::string dbConnection;public:DatabaseDataProcessor(const std::string& conn) : dbConnection(conn) {}// 实现读取数据:从数据库读取void readData() override {std::cout << "从数据库[" << dbConnection << "]读取数据" << std::endl;// 模拟读取数据data = {2.3, 4.5, 6.7, 8.9};}// 实现保存结果:保存到数据库void saveResult() override {std::cout << "将结果保存到数据库[" << dbConnection << "]" << std::endl;}// 重写钩子方法:不需要保存结果bool needSaveResult() override {return false; // 数据库数据可直接使用,无需额外保存}
};// 客户端代码:使用数据处理框架
int main() {// 处理文件数据std::cout << "=== 处理文件数据 ===" << std::endl;DataProcessor* fileProcessor = new FileDataProcessor("data.txt");fileProcessor->process(); // 调用模板方法,执行完整流程// 处理数据库数据std::cout << "\n=== 处理数据库数据 ===" << std::endl;DataProcessor* dbProcessor = new DatabaseDataProcessor("mysql://localhost:3306/data_db");dbProcessor->process();// 释放资源delete dbProcessor;delete fileProcessor;return 0;
}
三、代码解析
-
抽象类(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
,确保子类实现。 - 避免模板方法膨胀:当算法步骤过多时,可拆分抽象类(如使用组合模式),避免单个类过于庞大。
- 文档化模板流程:清晰注释模板方法的执行顺序和各步骤的作用,便于子类实现和维护。
模板方法模式的核心价值在于“标准化流程,个性化实现”,它通过分离算法的固定结构与可变步骤,既保证了系统的一致性,又为定制化需求提供了灵活的扩展点。在框架设计、流程管理等场景中,模板方法模式是实现代码复用和结构统一的重要手段。