模版方法模式(Template Method Pattern

发布于:2025-07-29 ⋅ 阅读:(15) ⋅ 点赞:(0)

模板方法模式是一种行为设计模式,它定义一个操作中的算法骨架,将一些步骤延迟到子类中实现。核心思想是"封装不变部分,扩展可变部分",即父类确定流程框架,子类根据需求重写特定步骤,而不改变整体流程结构。

介绍

核心角色
  1. 抽象类(Abstract Class):定义算法骨架(模板方法),包含多个抽象方法(子类需实现)和可选的钩子方法(Hook Method,子类可选择性重写)。
  2. 具体子类(Concrete Class):继承抽象类,实现抽象方法,完成算法中特定于子类的步骤。
优点
  1. 封装性好:算法骨架由父类控制,子类无需关心整体流程,只需专注于自身步骤实现。
  2. 扩展性强:新增子类时,只需重写特定方法即可扩展新功能,符合"开闭原则"。
  3. 代码复用:公共步骤在父类中实现,避免重复编码。
  4. 便于维护:流程变更时只需修改父类的模板方法,无需修改所有子类。
    模板方法模式通过分离"不变的流程"和"可变的步骤",实现了代码复用和灵活扩展,特别适合构建框架或统一多子类的核心流程。其核心是父类定骨架,子类填细节,在保证流程一致性的同时,最大化子类的灵活性。
适用场景
  1. 流程固定但步骤实现可变
    当多个子类的核心流程完全一致,仅部分步骤的实现不同时(如框架中的初始化流程、测试流程)。

  2. 需要控制子类扩展
    父类通过模板方法固定算法骨架,子类只能通过重写特定方法扩展,避免流程被随意修改(如final修饰模板方法)。

  3. 代码复用
    将公共步骤(如示例中的boilWater)在父类中实现,减少子类重复代码。

实现

以咖啡和茶的冲泡流程为例,两者的核心步骤相似(烧水→冲泡→倒杯→加调料),但具体实现不同,适合用模板方法模式统一流程。

#include <iostream>
#include <string>

// 抽象类:定义冲泡流程的模板方法
class Beverage {
public:
    // 模板方法:定义算法骨架(不可被子类修改)
    void prepareRecipe() final {
        boilWater();    // 烧水(公共步骤)
        brew();         // 冲泡(子类实现)
        pourInCup();    // 倒杯(公共步骤)
        if (customerWantsCondiments()) {  // 钩子方法:判断是否加调料
            addCondiments();  // 加调料(子类实现)
        }
    }

protected:
    // 抽象方法:子类必须实现
    virtual void brew() = 0;
    virtual void addCondiments() = 0;

    // 具体方法:父类实现,子类共享
    void boilWater() {
        std::cout << "烧开水(100℃)" << std::endl;
    }

    void pourInCup() {
        std::cout << "倒入杯子中" << std::endl;
    }

    // 钩子方法:默认返回true,子类可重写(可选步骤)
    virtual bool customerWantsCondiments() {
        return true;
    }
};

// 具体子类:咖啡
class Coffee : public Beverage {
protected:
    // 实现抽象方法:冲泡咖啡
    void brew() override {
        std::cout << "用沸水冲泡咖啡粉" << std::endl;
    }

    // 实现抽象方法:加调料
    void addCondiments() override {
        std::cout << "添加糖和牛奶" << std::endl;
    }

    // 重写钩子方法:询问用户是否加调料
    bool customerWantsCondiments() override {
        std::string answer;
        std::cout << "请问需要加奶和糖吗?(y/n)";
        std::cin >> answer;
        return answer == "y" || answer == "Y";
    }
};


// 具体子类:茶
class Tea : public Beverage {
protected:
    // 实现抽象方法:泡茶
    void brew() override {
        std::cout << "用沸水浸泡茶叶" << std::endl;
    }

    // 实现抽象方法:加调料
    void addCondiments() override {
        std::cout << "添加柠檬" << std::endl;
    }

    // 不重写钩子方法,使用父类默认实现(默认加调料)
};

int main() {
    std::cout << "=== 制作咖啡 ===" << std::endl;
    Beverage* coffee = new Coffee();
    coffee->prepareRecipe();  // 调用模板方法,执行完整流程

    std::cout << "\n=== 制作茶 ===" << std::endl;
    Beverage* tea = new Tea();
    tea->prepareRecipe();

    delete coffee;
    delete tea;
    return 0;
}
输出结果
=== 制作咖啡 ===
烧开水(100℃)
用沸水冲泡咖啡粉
倒入杯子中
请问需要加奶和糖吗?(y/n)y
添加糖和牛奶

=== 制作茶 ===
烧开水(100℃)
用沸水浸泡茶叶
倒入杯子中
添加柠檬
优点
  1. 钩子方法增强灵活性

    • 父类Beverage中的customerWantsCondiments()是钩子方法,默认返回true,子类可根据需求重写(如Coffee类通过用户输入决定是否加调料)。
    • 解决了"强制子类实现不需要的步骤"的问题,符合"最小知识原则"。
  2. 模板方法不可篡改

    • final修饰prepareRecipe(),确保子类无法修改整体流程(如不能跳过"烧水"步骤),保证框架的稳定性。
典型应用场景
  1. 框架设计

    • 框架(如MFC、Spring)定义核心流程(如InitApplication),用户通过重写钩子方法扩展功能。
    • 测试框架(如JUnit)的setUp()tearDown()方法,固定测试流程,用户实现具体测试逻辑。
  2. 文档生成工具

    • 统一文档结构(标题→摘要→正文→结论),子类实现不同类型文档(如报告、论文)的具体内容。
  3. 游戏开发

    • 角色技能释放流程(前摇→施法→伤害计算→后摇),不同技能重写"伤害计算"步骤。
  4. 资源加载

    • 资源加载流程(检查缓存→读取文件→解析数据→缓存),不同类型资源(图片、音频)重写"解析数据"步骤。

优化

#include <iostream>
#include <string>
#include <functional>

// 抽象基类:定义冲泡流程模板
class Beverage {
public:
    // 模板方法:用final禁止子类重写,保证流程固定
    void prepareRecipe() final {
        try {
            boilWater();          // 公共步骤:烧水
            brew();               // 抽象步骤:冲泡(子类实现)
            pourInCup();          // 公共步骤:倒杯
            if (customerWantsCondiments()) {  // 钩子方法:是否加调料
                addCondiments();  // 抽象步骤:加调料(子类实现)
            }
        } catch (const std::exception& e) {
            // 统一异常处理:避免子类重复编写
            std::cerr << "制作失败:" << e.what() << std::endl;
        }
    }

protected:
    // 抽象方法:子类必须实现
    virtual void brew() = 0;
    virtual void addCondiments() = 0;

    // 公共方法:父类实现,子类共享
    void boilWater() {
        std::cout << "烧开水(100℃)" << std::endl;
    }

    void pourInCup() {
        std::cout << "倒入杯子中" << std::endl;
    }

    // 钩子方法:子类可选择性重写(默认返回true)
    virtual bool customerWantsCondiments() {
        return true;  // 默认加调料
    }

    // 析构函数设为虚函数,确保子类正确析构
    virtual ~Beverage() = default;
};

class Coffee : public Beverage {
protected:
    // 实现抽象方法:冲泡咖啡
    void brew() override {
        std::cout << "用沸水冲泡咖啡粉" << std::endl;
    }

    // 实现抽象方法:加调料
    void addCondiments() override {
        std::cout << "添加糖和牛奶" << std::endl;
    }

    // 重写钩子方法:询问用户是否需要调料
    bool customerWantsCondiments() override {
        std::string answer;
        std::cout << "请问需要加奶和糖吗?(y/n)";
        std::cin >> answer;
        return answer == "y" || answer == "Y";
    }
};


class Tea : public Beverage {
public:
    // 支持动态设置加调料的方式(策略模式结合)
    using CondimentFunc = std::function<void()>;
    void setCondimentFunc(CondimentFunc func) {
        condimentFunc = func;
    }

protected:
    void brew() override {
        std::cout << "用沸水浸泡茶叶" << std::endl;
    }

    // 调用动态设置的调料函数
    void addCondiments() override {
        if (condimentFunc) {
            condimentFunc();
        } else {
            std::cout << "添加蜂蜜" << std::endl;  // 默认
        }
    }

    // 钩子方法:默认不加调料(覆盖父类行为)
    bool customerWantsCondiments() override {
        return true;  // 茶默认加调料
    }

private:
    CondimentFunc condimentFunc;  // 函数对象:动态步骤
};


int main() {
    std::cout << "=== 制作咖啡 ===" << std::endl;
    Beverage* coffee = new Coffee();
    coffee->prepareRecipe();  // 会询问是否加调料

    std::cout << "\n=== 制作柠檬茶 ===" << std::endl;
    Tea* tea = new Tea();
    // 动态设置加柠檬(无需创建新子类)
    tea->setCondimentFunc([](){
        std::cout << "添加柠檬片" << std::endl;
    });
    tea->prepareRecipe();

    std::cout << "\n=== 制作奶茶 ===" << std::endl;
    Tea* milkTea = new Tea();
    // 动态设置加牛奶(策略模式)
    milkTea->setCondimentFunc([](){
        std::cout << "添加牛奶和珍珠" << std::endl;
    });
    milkTea->prepareRecipe();

    delete coffee;
    delete tea;
    delete milkTea;
    return 0;
}
优化点
  1. 动态步骤替换(策略模式结合)

    • Tea类通过std::function允许动态设置addCondiments()的实现(如柠檬茶、奶茶),无需创建多个子类,减少代码冗余。
    • 适合步骤实现频繁变化的场景,比单纯继承更灵活。
  2. 统一异常处理

    • 模板方法中添加try-catch,子类无需单独处理异常,降低重复代码,且便于全局错误日志收集。
优化后的适用场景
  • 框架设计:需要严格控制核心流程,但允许用户灵活扩展部分步骤(如游戏引擎的渲染流程)。
  • 动态配置:步骤实现可能频繁变化(如不同用户的个性化需求),通过函数对象动态切换比创建子类更高效。
  • 异常敏感场景:需要统一错误处理的系统(如金融交易流程),避免子类遗漏异常处理。

网站公告

今日签到

点亮在社区的每一天
去签到