什么是模板方法模式?

模板方法模式是一种行为型设计模式,它定义了一个操作的骨架(算法框架),将某些具体步骤的实现延迟到子类中,从而使得子类可以重新定义算法的某些步骤,而不改变算法的结构。

通俗解释:

假设我们需要泡一杯茶或者冲一杯咖啡,步骤大致是:

  1. 煮水。
  2. 冲泡茶叶或咖啡。
  3. 倒入杯中。
  4. 加配料(比如加糖、加牛奶)。

这些步骤中,煮水倒入杯中是固定的,属于模板,而冲泡内容(茶或咖啡)加配料则可以根据具体情况进行定制。

模板方法模式的核心就是定义一个模板方法(固定步骤的框架),让子类来实现某些特定步骤的逻辑


模板方法模式的特点

  1. 固定算法的骨架:模板方法定义了算法的固定流程。
  2. 可扩展:子类可以实现特定的步骤,而无需改变算法的整体结构。
  3. 遵循开闭原则:算法框架对扩展开放,但对修改封闭。

模板方法模式的结构

UML 类图
        +----------------------+
        |   AbstractClass      |   // 抽象类
        +----------------------+
        |  + templateMethod()  |   // 模板方法(定义流程)
        |  + step1()           |   // 具体方法(固定实现)
        |  + step2() = 0       |   // 抽象方法(延迟实现)
        +----------------------+
                   ^
                   |
        +----------------------+
        |   ConcreteClass      |   // 具体实现类
        +----------------------+
        |  + step2()           |   // 实现抽象方法
        +----------------------+
组成部分:
  1. AbstractClass(抽象类)

    • 定义算法的骨架。
    • 包含一个templateMethod模板方法,描述操作的固定流程。
    • 包含一些具体方法(已实现)和抽象方法(需子类实现)。
  2. ConcreteClass(具体实现类)

    • 实现AbstractClass中的抽象方法。
    • 可以对模板方法中的特定步骤进行定制。
  3. templateMethod(模板方法)

    • 在抽象类中定义,用于描述算法的整体流程。

模板方法模式的优点和缺点

优点:
  1. 代码复用:将通用的代码放在抽象类中,减少重复代码。
  2. 灵活性高:允许子类通过实现特定的步骤来扩展功能。
  3. 符合开闭原则:算法骨架固定,子类可以自由扩展具体步骤。
缺点:
  1. 继承限制:需要通过继承来实现,可能会导致类的数量增加。
  2. 灵活性有限:模板方法的框架是固定的,若需要大幅更改流程,则需要修改抽象类。

案例:饮品制作(冲咖啡与泡茶)

需求描述

我们需要设计一个饮品制作流程,支持以下两种饮品:

  1. 咖啡

制作饮品的流程是固定的,但某些步骤(如冲泡和加配料)根据具体饮品有所不同。


代码实现

以下是完整的代码实现。

#include <iostream>
#include <string>

// 抽象类:饮品
class Beverage {
public:
    // 模板方法:定义制作饮品的流程
    void prepareRecipe() {
        boilWater();          // 煮水(固定步骤)
        brew();               // 冲泡(具体饮品不同)
        pourInCup();          // 倒入杯中(固定步骤)
        addCondiments();      // 加配料(具体饮品不同)
    }

protected:
    // 固定步骤:煮水
    void boilWater() {
        std::cout << "将水煮沸。" << std::endl;
    }

    // 固定步骤:倒入杯中
    void pourInCup() {
        std::cout << "将饮品倒入杯中。" << std::endl;
    }

    // 抽象步骤:冲泡(需要子类实现)
    virtual void brew() = 0;

    // 抽象步骤:加配料(需要子类实现)
    virtual void addCondiments() = 0;

    // 虚析构函数,保证子类正确释放资源
    virtual ~Beverage() = default;
};

// 具体类:咖啡
class Coffee : public Beverage {
protected:
    // 实现冲泡步骤
    void brew() override {
        std::cout << "用沸水冲泡咖啡。" << std::endl;
    }

    // 实现加配料步骤
    void addCondiments() override {
        std::cout << "加入糖和牛奶。" << std::endl;
    }
};

// 具体类:茶
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;
    Coffee coffee;
    coffee.prepareRecipe();

    std::cout << "\n制作茶:" << std::endl;
    Tea tea;
    tea.prepareRecipe();

    return 0;
}

运行结果

运行以上代码后,输出如下:

制作咖啡:
将水煮沸。
用沸水冲泡咖啡。
将饮品倒入杯中。
加入糖和牛奶。

制作茶:
将水煮沸。
用沸水浸泡茶叶。
将饮品倒入杯中。
加入柠檬。

代码讲解

1. 抽象类(Beverage)
class Beverage {
public:
    void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

protected:
    void boilWater() {
        std::cout << "将水煮沸。" << std::endl;
    }

    void pourInCup() {
        std::cout << "将饮品倒入杯中。" << std::endl;
    }

    virtual void brew() = 0; // 冲泡(抽象方法)
    virtual void addCondiments() = 0; // 加配料(抽象方法)
};
  • **prepareRecipe**是模板方法,定义了制作饮品的固定流程。
  • **boilWaterpourInCup**是固定步骤,所有子类都共享。
  • **brewaddCondiments**是抽象步骤,需要具体子类实现。

2. 具体类(Coffee 和 Tea)
class Coffee : public Beverage {
protected:
    void brew() override {
        std::cout << "用沸水冲泡咖啡。" << std::endl;
    }

    void addCondiments() override {
        std::cout << "加入糖和牛奶。" << std::endl;
    }
};
  • Coffee类实现了brewaddCondiments方法,用于描述咖啡的具体冲泡和加配料逻辑。
  • Tea类则实现了适合茶的冲泡和加配料逻辑。

3. 客户端代码
Coffee coffee;
coffee.prepareRecipe();

Tea tea;
tea.prepareRecipe();
  • 客户端创建CoffeeTea对象,并调用模板方法prepareRecipe来完成饮品制作。

模板方法模式的优缺点

优点:
  1. 复用固定流程代码:将通用的逻辑写入抽象类,减少代码重复。
  2. 灵活扩展:子类只需实现自己的步骤逻辑,无需修改模板方法的整体流程。
  3. 符合开闭原则:算法框架固定,对修改封闭;具体步骤对扩展开放。
缺点:
  1. 继承关系限制:模板方法模式依赖继承,如果需要使用多个模板,可能导致继承关系复杂。
  2. 细节依赖抽象类:所有具体类都必须依赖抽象类,增加了类的依赖关系。

适用场景

  1. 需要复用固定流程的代码
    • 比如算法的固定步骤(数据清洗、训练、测试等)。
  2. 不同子类有不同实现的场景
    • 比如文件解析器,整体流程是固定的,但具体解析逻辑可能不同。
  3. 复杂流程的抽象
    • 比如生产流水线,流程固定但具体实现由不同生产线决定。

总结

模板方法模式是一个非常经典的设计模式,适合用在需要固定流程且可扩展步骤的场景中。在本案例中,我们通过模板方法模式实现了饮品的制作流程,充分展示了其固定框架+灵活扩展的特点。

模板方法模式的核心在于封装不变部分,扩展可变部分,是一种非常实用且高效的设计模式!****

更多推荐