C++设计模式之行为型模式:模板方法模式(Template Method)

模板方法模式(Template Method)是行为型设计模式的一种,它定义了一个算法的骨架,将算法的一些步骤延迟到子类中实现。这种模式允许子类在不改变算法结构的情况下,重新定义算法中的某些步骤,从而实现算法的复用与定制。

一、核心思想与角色

模板方法模式的核心是"固定流程,可变步骤",通过在父类中定义算法的框架,将可变部分委托给子类实现。其核心角色如下:

角色名称 核心职责
抽象类(AbstractClass) 定义算法的骨架(模板方法),包含多个抽象方法(子类必须实现的步骤)和可选的钩子方法(子类可重写的步骤)。
具体子类(ConcreteClass) 实现抽象类中的抽象方法,完成算法中与具体子类相关的步骤,可选择性重写钩子方法。

核心思想:父类封装不变的算法结构,子类负责实现具体的可变步骤,既保证了算法结构的一致性,又允许子类灵活定制部分步骤。

二、实现示例(数据处理流程)

假设我们需要设计一个数据处理框架,流程包括:数据读取、数据清洗、数据处理、结果保存四个步骤。其中,数据读取和结果保存的方式可能因数据来源(文件、数据库)不同而变化,但数据清洗和处理的核心逻辑固定。使用模板方法模式可复用核心流程,同时允许定制读取和保存步骤:

cpp 复制代码
#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;
}

三、代码解析

  1. 抽象类(DataProcessor)

    • 模板方法(process()) :用final修饰确保子类不能重写,定义了数据处理的完整流程:readData() → cleanData() → analyzeData() → saveResult() → postProcess()
    • 抽象方法readData()saveResult()声明为纯虚方法,要求子类必须实现(因不同数据源的读取和保存方式不同)。
    • 固定方法cleanData()analyzeData()protected的具体实现,封装了所有子类共享的核心逻辑,子类不能重写但可调用。
    • 钩子方法needSaveResult()(默认返回true)和postProcess()(默认空实现)允许子类选择性重写,用于定制流程中的可选步骤。
  2. 具体子类

    • FileDataProcessor:实现从文件读取和保存数据,重写postProcess()添加可视化处理。
    • DatabaseDataProcessor:实现从数据库读取数据,重写needSaveResult()返回false(无需额外保存结果)。
      两个子类都复用了process()定义的流程,仅定制了需要变化的步骤。
  3. 客户端使用

    客户端通过抽象类接口创建具体处理器并调用process(),无需关心流程细节,只需确保子类正确实现了抽象方法。

四、核心优势与适用场景

优势
  1. 代码复用:将算法中不变的部分集中在父类,避免子类重复实现,提高代码复用率。
  2. 结构统一:模板方法固定了算法结构,确保所有子类遵循相同的流程,维护一致性。
  3. 灵活定制:子类可通过重写抽象方法和钩子方法,定制算法中的可变步骤,符合开闭原则。
  4. 反向控制:父类控制流程,子类提供实现,体现"好莱坞原则"("不要调用我们,我们会调用你")。
适用场景
  1. 流程固定但步骤可变:如框架设计(请求处理、数据解析、工作流引擎)、生命周期管理(初始化→运行→销毁)。
  2. 多个子类共享核心逻辑:当多个子类有相同的步骤流程,仅部分步骤实现不同时。
  3. 需要控制子类扩展:通过模板方法限制子类只能扩展特定步骤,避免破坏算法结构。

五、与其他模式的区别

模式 核心差异点
模板方法 父类定义算法骨架,子类实现具体步骤,强调"固定流程+可变实现"。
策略模式 封装不同算法,客户端动态选择,算法间相互独立,无流程依赖。
工厂方法 父类定义创建对象的接口,子类决定具体创建哪种对象,专注于对象创建。
观察者模式 定义对象间的一对多依赖,强调状态变化通知,与算法流程无关。

六、实践建议

  1. 合理设计钩子方法:钩子方法应仅用于影响流程的可选步骤,避免过度使用导致流程混乱。
  2. 控制方法访问权限 :固定步骤设为privateprotected final防止子类重写,抽象步骤设为protected pure virtual,确保子类实现。
  3. 避免模板方法膨胀:当算法步骤过多时,可拆分抽象类(如使用组合模式),避免单个类过于庞大。
  4. 文档化模板流程:清晰注释模板方法的执行顺序和各步骤的作用,便于子类实现和维护。

模板方法模式的核心价值在于"标准化流程,个性化实现",它通过分离算法的固定结构与可变步骤,既保证了系统的一致性,又为定制化需求提供了灵活的扩展点。在框架设计、流程管理等场景中,模板方法模式是实现代码复用和结构统一的重要手段。

相关推荐
只是懒得想了3 小时前
用C++实现一个高效可扩展的行为树(Behavior Tree)框架
java·开发语言·c++·design-patterns
我是华为OD~HR~栗栗呀3 小时前
华为OD-23届考研-Java面经
java·c++·后端·python·华为od·华为·面试
o0向阳而生0o3 小时前
108、23种设计模式之模板方法模式(17/23)
设计模式·模板方法模式
mit6.8243 小时前
pq|二维前缀和
c++
_poplar_4 小时前
15 【C++11 新特性】统一的列表初始化和变量类型推导
开发语言·数据结构·c++·git·算法
Yupureki4 小时前
从零开始的C++学习生活 7:vector的入门使用
c语言·c++·学习·visual studio
奋斗羊羊5 小时前
【C++】使用MSBuild命令行编译ACE、TAO、DDS
开发语言·c++·windows
史迪奇_xxx6 小时前
9、C/C++ 内存管理详解:从基础到面试题
java·c语言·c++
晨非辰6 小时前
《超越单链表的局限:双链表“哨兵位”设计模式,如何让边界处理代码既优雅又健壮?》
c语言·开发语言·数据结构·c++·算法·面试