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

作者:bkspiderx日期:2025/10/15

模板方法模式(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

三、代码解析

  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. 文档化模板流程:清晰注释模板方法的执行顺序和各步骤的作用,便于子类实现和维护。

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


C++设计模式之行为型模式:模板方法模式(Template Method)》 是转载文章,点击查看原文


相关推荐


苦练Python第64天:从零掌握多线程,threading模块全面指南
倔强青铜三 VIP.1 初学乍练2025/10/14

前言 大家好,我是倔强青铜三。欢迎关注我,微信公众号:倔强青铜三。点赞、收藏、关注,一键三连! 欢迎继续 苦练Python第64天。 今天咱们把“并发”这把瑞士军刀——threading 模块,从开箱到实战一次性讲透。全程只用 Python 自带标准库,代码复制即可运行! 一、为什么需要线程? I/O 密集场景:爬虫、文件下载、日志采集,CPU 在等网络/磁盘,闲着也是闲着。 共享内存:比多进程轻量,数据不用序列化来回拷贝。 GIL?别慌:I/O 密集时线程照样提速;CPU 密集请转投 mu


局域网IP地址冲突排查与解决全指南:从诊断到预防
Bruce_xiaowei2025/10/12

局域网IP地址冲突排查与解决全指南:从诊断到预防 在局域网管理和维护中,IP地址冲突是一个常见但令人头疼的问题。当两台或多台设备被分配了相同的IP地址时,网络连接就会变得不稳定甚至中断。本文将详细介绍如何快速定位、解决并预防IP地址冲突问题。 IP地址冲突的识别与现象 典型症状表现: 设备网络连接时断时续频繁出现"网络电缆被拔出"提示Ping测试出现"一般故障"或"请求超时"特定网络服务无法访问 冲突根源分析: 手动配置IP地址时出现重复分配DHCP服务器范围设置不当网络中存在未经授权的DHCP


领码方案|微服务与SOA的世纪对话(5):未来已来——AI 驱动下的智能架构哲学
领码科技2025/10/11

📌 摘要 AI 已从工具升级为架构的“新大脑”,成为边界、治理、交付与演进的核心驱动力。本文按「方法论新生」模板,聚焦 AI 驱动下的智能架构哲学: 用智能双生体强化领域与基础设施模型用AI 增强 DDD 与契约,让边界自动进化用自驱动 Service Mesh,实现策略的智能演化用预测型 CI/CD,让发布成为预判与优化的闭环构建自演进反馈体系,让系统具备持续自优化能力 结合未来架构趋势与实践路径,给出端到端流程与行动清单,帮助组织完成从“自动化”到“智能化”的跃迁。 关键词:智能双


【LeetCode - 每日1题】水位上升的泳池中游泳问题
(时光煮雨)2025/10/9

🌈 个人主页:(时光煮雨) 🔥 高质量专栏:vulnhub靶机渗透测试 👈 希望得到您的订阅和支持~ 💡 创作高质量博文(平均质量分95+),分享更多关于网络安全、Python领域的优质内容!(希望得到您的关注~) 🌵目录🌵 难度 ⭐⭐⭐⭐⭐ 题目回顾 ✅解题思路分析 💖 概述 💓 核心思路 ✅ 代码分析 ✅ 复杂度分析 ✅ 测试用例验证 ✅


Flex 布局下文字省略不生效?原因其实很简单
序猿杂谈2025/10/8

概述 在开发中,我们常遇到这种布局:A、B、C 三段文本长度不固定,其中 A、C 占用空间较少,需要完整显示;而 B 是自适应区域,超出部分需省略显示。A 与 B 共享同一容器,右侧的 “Live” 按钮与该容器同级,容器应占据除 “Live” 之外的所有剩余空间。 HTML 结构如下: <div class="wrap"> <div class="infos"> <div>Soccer</div> <div class="self">Campeonato De Reser


Python 的内置函数 bin
IMPYLH2025/10/6

Python 内建函数列表 > Python 的内置函数 bin bin()函数是Python内置的一个简单但强大的工具,它能够将整数转换为以"0b"为前缀的二进制字符串表示形式。在计算机科学中,二进制是基础中的基础,理解二进制表示对于学习位运算、硬件接口编程、数据压缩等领域都至关重要。 bin 的函数原型如下所示: def bin(x:int): ''' 将一个整数转换为带前缀 `0b` 的二进制字符串 :param x: 一个整数 :return: x


MySQL Exporter采集的关键指标有哪些,如何解读这些指标?
IT橘子皮2025/10/5

MySQL Exporter 是 Prometheus 生态中用于监控 MySQL 数据库的核心工具,通过采集丰富的性能指标帮助用户分析数据库运行状态。以下是其关键指标分类及解读方法: 一、核心指标分类与解读 1. ​数据库可用性与基础状态​ ​**mysql_up**​ 值为 1表示 MySQL 实例正常运行,0表示不可用。用于快速判断数据库是否存活。 ​**mysql_global_status_uptime**​ 数据库已运行时间(秒)。若突然归零可能表示实例重启,需结合告警规则


从技术史看:Unix 从何而来
大聪明-PLUS2025/10/4

大家好!我是大聪明-PLUS! 如今,许多人认为“Unix”和“Linux”是同一个东西。但从 2024 年起,对于大多数我们认为是“Unix”和“Linux”的发行版来说,情况基本如此。 但 Unix 的历史悠久。如果你只了解我们今天所熟知的 Linux 系统,很难想象“Unix 早期的情况是什么样的”,因为自那时以来发生了太多变化。 ❯ 从原型到 Unix 让我们回顾一下 Unix 的起源。1969 年,贝尔实验室的研究员 Ken Thompson 正在尝试操作系统设计。 当时,贝


笔记本 光驱 的内部结构及用法: 应急系统启动 (恢复) 光盘 (DVD+R/RW)
穷人小水滴2025/10/3

光盘 (CD/DVD/BD) 基本上是一种被淘汰的古老存储技术了, 然而在特定领域, 光盘仍然具有明显的使用价值, 宝刀未老. 低成本 (特别是单张光盘很便宜), 防水防磁耐摔, 只读 (不可修改, 比如 DVD+R, BD-R), 读写设备与存储分离, 这些优点至今难以超越. 笔记本光驱 (轻薄小, 9 ~ 13mm 厚) (二手) 淘宝价约 30 元/个, 5.25 英寸 SATA 大光驱 (台式) (二手) 淘宝价约 20 元/个, 单张光盘 (全新) 只需 2 元. 本文介绍目前还能用的


unzip-6.0-21.el7.x86_64.rpm怎么安装?CentOS 7手动安装rpm包详细步骤
心灵宝贝2025/10/2

本文介绍了如何在 ​CentOS 7​ 系统上手动安装 unzip-6.0-21.el7.x86_64.rpm 这个 RPM 包,包括下载后如何用终端命令进行安装、常见问题的解决方法,以及如何验证是否安装成功 一、确保你有这个 rpm 文件 首先,你电脑上得有这个文件,比如它叫: ​unzip-6.0-21.el7.x86_64.rpm​ 安装包下载:https://pan.quark.cn/s/34cd3075c0fd 二、打开终端(命令行) 在 Linux 系统里(比如 Ce

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0