12.C++设计模式-模板方法模式

1. 概念

模板方法模式是一种行为设计模式,它在父类中定义一个算法的骨架,允许子类在不改变算法结构的情况下重定义算法的某些步骤。

2. 核心组成

<<abstract>>
AbstractClass
+TemplateMethod()
#PrimitiveOperation1()
#PrimitiveOperation2()
+HookMethod()
ConcreteClassA
#PrimitiveOperation1()
#PrimitiveOperation2()
ConcreteClassB
#PrimitiveOperation1()
#PrimitiveOperation2()

3. 角色说明

  • 抽象类(AbstractClass):定义模板方法和基本操作
  • 具体类(ConcreteClass):实现基本操作
  • 模板方法(TemplateMethod):定义算法骨架
  • 基本操作(PrimitiveOperation):子类必须实现的抽象方法
  • 钩子方法(HookMethod):可选扩展点

4. 应用场景

  • 数据库操作模板(连接、查询、关闭)
  • 游戏开发(游戏初始化、开始、结束)
  • 饮料制作(煮水、冲泡、倒杯、加调料)
  • 数据挖掘(数据读取、分析、输出)
  • 算法框架(排序、搜索算法的固定流程)

5. C++代码示例

示例1:饮料制作
cpp 复制代码
#include <iostream>
using namespace std;

// 抽象类:饮料
class Beverage {
public:
    // 模板方法 - 制作饮料的完整流程
    void MakeBeverage() {
        BoilWater();
        Brew();
        PourInCup();
        AddCondiments();
        
        // 钩子方法示例
        if (WantSugar()) {
            AddSugar();
        }
    }
    
protected:
    void BoilWater() {
        cout << "把水煮沸" << endl;
    }
    
    void PourInCup() {
        cout << "倒入杯子中" << endl;
    }
    
    // 抽象方法,子类必须实现
    virtual void Brew() = 0;
    virtual void AddCondiments() = 0;
    
    // 钩子方法,子类可选择覆盖
    virtual bool WantSugar() {
        return false;  // 默认不加糖
    }
    
    void AddSugar() {
        cout << "加糖" << endl;
    }
};

// 具体类:咖啡
class Coffee : public Beverage {
protected:
    void Brew() override {
        cout << "冲泡咖啡粉" << endl;
    }
    
    void AddCondiments() override {
        cout << "加牛奶和糖" << endl;
    }
    
    bool WantSugar() override {
        char choice;
        cout << "咖啡是否需要加糖?(y/n): ";
        cin >> choice;
        return (choice == 'y' || choice == 'Y');
    }
};

// 具体类:茶
class Tea : public Beverage {
protected:
    void Brew() override {
        cout << "浸泡茶叶" << endl;
    }
    
    void AddCondiments() override {
        cout << "加柠檬" << endl;
    }
};

// 使用示例
int main() {
    cout << "=== 制作咖啡 ===" << endl;
    Coffee coffee;
    coffee.MakeBeverage();
    
    cout << "\n=== 制作茶 ===" << endl;
    Tea tea;
    tea.MakeBeverage();
    
    return 0;
}
示例2:数据处理器
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 抽象数据处理器
class DataProcessor {
public:
    // 模板方法
    void ProcessData() {
        if (ValidateData()) {
            LoadData();
            AnalyzeData();
            OutputResult();
        } else {
            cout << "数据验证失败!" << endl;
        }
    }
    
protected:
    vector<int> data;
    
    // 钩子方法:数据验证
    virtual bool ValidateData() {
        return !data.empty();
    }
    
    // 抽象方法
    virtual void LoadData() = 0;
    virtual void AnalyzeData() = 0;
    virtual void OutputResult() = 0;
};

// 排序处理器
class SortProcessor : public DataProcessor {
protected:
    void LoadData() override {
        data = {5, 2, 8, 1, 9, 3};
        cout << "加载数据: ";
        PrintData();
    }
    
    void AnalyzeData() override {
        sort(data.begin(), data.end());
        cout << "执行排序分析" << endl;
    }
    
    void OutputResult() override {
        cout << "排序结果: ";
        PrintData();
    }
    
private:
    void PrintData() {
        for (int num : data) {
            cout << num << " ";
        }
        cout << endl;
    }
};

// 统计处理器
class StatisticsProcessor : public DataProcessor {
protected:
    void LoadData() override {
        data = {10, 20, 30, 40, 50};
        cout << "加载统计数据: ";
        PrintData();
    }
    
    void AnalyzeData() override {
        sum = 0;
        for (int num : data) {
            sum += num;
        }
        average = sum / data.size();
        cout << "计算统计指标" << endl;
    }
    
    void OutputResult() override {
        cout << "数据统计结果:" << endl;
        cout << "总和: " << sum << endl;
        cout << "平均值: " << average << endl;
        cout << "数据个数: " << data.size() << endl;
    }
    
private:
    int sum;
    int average;
    
    void PrintData() {
        for (int num : data) {
            cout << num << " ";
        }
        cout << endl;
    }
};

int main() {
    cout << "=== 排序处理器 ===" << endl;
    SortProcessor sortProc;
    sortProc.ProcessData();
    
    cout << "\n=== 统计处理器 ===" << endl;
    StatisticsProcessor statProc;
    statProc.ProcessData();
    
    return 0;
}

6. 优缺点分析

缺点
维护成本
增加代码复杂度
继承耦合
子类受父类约束
理解难度
初学者理解困难
优点
代码复用
减少重复代码
扩展性
易于添加新实现
控制反转
父类控制算法

7. 最佳实践建议

  1. 合理使用钩子方法:提供适当的扩展点
  2. 明确抽象粒度:抽象方法不要过多过细
  3. 遵守好莱坞原则:Don't call us, we'll call you
  4. 组合优于继承:考虑是否真的需要使用模板方法模式

8.模板方法模式的思想思维深度解析

8.1 核心思想:控制反转与好莱坞原则

模板方法模式最核心的思想是 "Don't call us, we'll call you"(别打电话给我们,我们会打给你)。
模板方法思维
规定流程
反向调用
反向调用
规定流程
父类模板方法
步骤1
子类实现步骤2
子类实现步骤3
步骤4
传统思维
主动调用
主动调用
主动调用
主程序
子程序A
子程序B
子程序C

思维转变:从"我来调用工具"转变为"框架来调度我"。

8.2 抽象思维:骨架与细节的分离

稳定vs变化
包含
包含
包含
包含
包含
包含
稳定的算法骨架

不易变化
步骤顺序
流程结构
公共行为
变化的具体步骤

容易变化
实现细节
算法变体
业务差异

思维要点

  • 识别不变:什么流程是永远不变的?
  • 封装变化:什么细节是可能改变的?
  • 分离关注:框架作者关心"骨架",应用开发者关心"细节"
8.3 哲学思维:契约式设计
cpp 复制代码
class GameFramework {
public:
    // 这是框架与开发者之间的"契约"
    // 框架保证:按照这个顺序调用
    // 开发者保证:实现这些方法
    void RunGame() {
        Initialize();   // 步骤1
        LoadAssets();   // 步骤2
        while (isRunning) {
            ProcessInput();  // 步骤3
            Update();        // 步骤4
            Render();        // 步骤5
        }
        Cleanup();      // 步骤6
    }
    
protected:
    virtual void Initialize() = 0;    // 开发者必须实现
    virtual void ProcessInput() = 0;   // 开发者必须实现
    virtual void Update() = 0;         // 开发者必须实现
    virtual void Render() = 0;         // 开发者必须实现
    
    // 钩子方法:可选的扩展点
    virtual void LoadAssets() {}
    virtual void Cleanup() {}
    
private:
    bool isRunning = true;
};

思维意义

  • 框架作者:"我会在正确的时间调用你的代码"
  • 应用开发者:"我只用关心具体业务逻辑"
8.4 结构化思维:模板方法的生命周期

模板方法调用
步骤检查:前置验证
步骤检查
步骤1执行
钩子判断1:可选步骤
钩子判断1
步骤2执行:如果钩子返回true
步骤3执行:如果钩子返回false
步骤2执行
步骤3执行
钩子判断2:后置处理
钩子判断2
步骤4执行:可选后置
步骤4执行

8.5 类比思维:现实世界的模板方法

餐厅用餐流程
可变细节
不同餐厅

菜单不同
吃面条/米饭

用筷子/刀叉
现金/刷卡/扫码
固定流程
进店
点餐
用餐
结账
离开

软件开发类比

  • 框架 = 餐厅管理规范
  • 模板方法 = 服务流程
  • 钩子方法 = 特色服务(餐后送水果)
  • 具体类 = 具体的餐厅(肯德基vs海底捞)
8.6 工程思维:稳定性与扩展性的平衡
cpp 复制代码
// 体现稳定性的代码
class StableWorkflow {
public:
    void Execute() final {  // final禁止子类修改流程
        Step1();
        Step2();
        if (Condition()) {
            Step3();
        }
        Step4();
    }
    
protected:
    void Step1() { /* 稳定的实现 */ }
    void Step2() { /* 稳定的实现 */ }
    void Step4() { /* 稳定的实现 */ }
    
    // 扩展点
    virtual bool Condition() { return true; }
    virtual void Step3() = 0;  // 必须扩展的点
};

工程智慧

  • 8/2法则:80%的代码稳定,20%的代码变化
  • 最小惊讶原则:流程应该符合直觉
  • 开放-封闭原则:对扩展开放,对修改关闭
8.7 系统思维:模板方法在架构中的位置

设计模式关系
架构层次
调用
反向调用
限制
驱动
包含
可与组合
可与结合
应用层
模板方法

框架层
具体实现

插件层
系统约束
业务需求
工厂方法
策略模式
观察者模式

8.8 辩证思维:何时使用,何时避免

使用场景(绿色):

  • ✅ 多个类有相同的算法骨架
  • ✅ 需要控制子类扩展的点
  • ✅ 需要重构重复代码到父类
  • ✅ 框架设计中的标配

避免场景(红色):

  • ❌ 只有一个具体实现(过度设计)
  • ❌ 算法步骤变化频繁(策略模式更好)
  • ❌ 子类需要覆盖大部分步骤(设计有问题)
  • ❌ 继承深度过大(违反组合优先原则)
8.9 思维升华:从设计模式到架构思维
cpp 复制代码
// 这就是微服务的模板方法思维
class MicroserviceFramework {
public:
    void Run() {  // 这就是"服务网格"的控制逻辑
        RegisterService();
        ConnectToConfigCenter();
        SetupMetrics();
        
        if (EnableTracing()) {  // 钩子:是否启用链路追踪
            SetupTracing();
        }
        
        StartServer();
        HandleGracefulShutdown();
    }
    
protected:
    virtual void StartServer() = 0;
    virtual void HandleGracefulShutdown() = 0;
    virtual bool EnableTracing() { return false; }
    virtual void SetupTracing() {}
};
8.10 核心思维总结
思维维度 核心要点 境界
控制思维 反转控制,框架主导 信任框架
抽象思维 分离不变与变化 识别边界
契约思维 明确接口约定 建立规范
系统思维 考虑整体架构 全局视野
工程思维 权衡稳定与扩展 实用主义

最后的思考

模板方法模式本质上是一种元思维------你不仅在解决当前问题,更是在思考"如何组织未来可能变化的解决方案"。它教会我们:

  1. 预见变化:在设计时就考虑哪些部分可能会变化
  2. 建立框架:先构建稳定的骨架,再填充细节
  3. 反转视角:从"被调用者"转变为"调用者"
  4. 契约精神:明确谁负责什么,建立清晰的边界

这不仅仅是一个编码技巧,更是一种架构思维------好的架构师会设计出既稳定又灵活的"框架",让团队成员在这个框架下各司其职,共同构建复杂的系统。

相关推荐
江屿风4 小时前
【C++笔记】内存管理流食般投喂
开发语言·c++·笔记
雪度娃娃4 小时前
行为型设计模式——备忘录模式
服务器·c++·设计模式·备忘录模式
khalil10204 小时前
代码随想录算法训练营Day-55 图论06 | 108.冗余连接、109.冗余连接II
c++·算法·leetcode·图论·并查集
进击的荆棘4 小时前
优选算法——字符串
开发语言·c++·算法·leetcode·字符串
山栀shanzhi4 小时前
长连接、短连接、心跳、断线重连
开发语言·网络·c++
玖釉-4 小时前
C++ 动态规划经典题:戳气球问题详解——从区间 DP 到状态转移
c++·动态规划
洛水水5 小时前
数据库连接池详解
数据库·c++·mysql
皮卡祺q5 小时前
【抽奖系统-0】Redis 缓存与 RabbitMQ 削峰实战;架构梳理
设计模式
码小猿的CPP工坊5 小时前
AI时代C++软件开发工程师的思考
c++·人工智能