设计模式:模板方法模式

目录

一、引言

二、优化前的代码

三、模板方法模式

四、优化后的代码

五、应用场景

六、结语


一、引言

老样子,先来看一个具体的简单例子。

冲饮料的固定流程:

  1. 把水烧开
  2. 冲泡(咖啡 / 茶不一样)
  3. 倒入杯子
  4. 加配料(糖 / 牛奶 / 柠檬不一样)

其中,1、3步骤是固定的,不管冲什么饮料都一样。2、4步骤是可变的,取决于要冲什么饮料。

整个冲饮料的顺序就是模板方法。现在不理解没关系,看完你就明白了。

二、优化前的代码

用代码实现上面的例子。

cpp 复制代码
#include <iostream>
#include <string>

class Drink {
public:
    Drink(const std::string& name):_name(name) {}
    void makeDrink() {
        boilWater();     // 烧水
        brew();          // 冲泡
        pourInCup();     // 倒入杯子中
        addCondiments(); // 添加配料
    }
private:
    void boilWater() {
        std::cout << "烧水!"<< std::endl;
    }
    void brew() {
        if (_name == "茶") {
            std::cout << "冲茶!" << std::endl;
        }else if (_name == "咖啡") {
            std::cout << "冲咖啡!" << std::endl;
        }
    }
    void pourInCup() {
        std::cout << "倒入杯子中!" << std::endl;
    }
    void addCondiments() {
        if (_name == "茶") {
            std::cout << "加白糖!" << std::endl;
        }else if (_name == "咖啡") {
            std::cout << "加牛奶!" << std::endl;
        }
    }
private:
    std::string _name;
};

int main() {
    Drink* d1 = new Drink("茶");
    d1->makeDrink();

    Drink* d2 = new Drink("咖啡");
    d2->makeDrink();

    delete d1;
    delete d2;
    return 0;
}

这个代码有什么问题?

如果要冲罗汉果茶,那就要修改Drink类中的代码,在brew()函数和addCondiments()函数中增加一个else if。这就违背了开闭原则------对扩展开放,对修改关闭。

仅凭这一点,这段代码就不是一段好代码。

三、模板方法模式

我们来看看它的定义:

定义一个操作中的算法骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以不改变一个算法的结构,即可重新定义该算法的某些特定步骤。

是不是感觉说了跟没说一样?

这个毕竟是教科书上的标准定义,抽象很正常。

结合结构图,重新解释这个定义。

在抽象类中定义一个模板方法,这个模板方法规定了业务流程的执行顺序与骨架。对应到上面代码中,makeDrink方法就是模板方法,模板方法中调用的所有函数,叫做基本方法。

流程中的有些步骤,可以声明为抽象方法,具体的实现交给子类重写实现。

子类通过继承抽象类、实现抽象方法,来完成流程的个性化。

对应到上面代码中,brew()函数和addCondiments()函数就可以声明为抽象方法。具体要冲什么饮料、加什么配料由子类来具体实现。

总的来说就是,父类的模板方法定流程骨架,子类通过重写填步骤细节,使得同一套流程可以有不同的结果。

四、优化后的代码

下面,应用模板方法模式对代码进行优化。

cpp 复制代码
#include <iostream>
#include <string>

class Drink {
public:
    // 流程是固定死的
    void makeDrink() {
        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;
};

class DrinkTea : public Drink {
private:
    virtual void brew() override {
        std::cout << "泡茶" << std::endl;
    }
    virtual void addCondiments() override {
        std::cout << "加白糖" << std::endl;
    }
};

class DrinkCoffee : public Drink {
private:
    virtual void brew() override {
        std::cout << "冲咖啡" << std::endl;
    }
    virtual void addCondiments() override {
        std::cout << "加牛奶" << std::endl;
    }
};

int main() {
    std::cout << "=========================制作茶========================:" << std::endl;
    Drink* d1 = new DrinkTea();
    d1->makeDrink();

    std::cout << "=========================制作咖啡========================:" << std::endl;
    Drink* d2 = new DrinkCoffee();
    d2->makeDrink();

    delete d1;
    delete d2;

    return 0;
}

如果要制作罗汉果茶,那么只需要增加一个子类继承父类并实现抽象方法即可。

我们看看优化后的代码都符合哪些设计原则。

  1. 开闭原则:制作新的饮料不改父类的代码。
  2. 单一职责:只负责制作饮料。

这两个设计原则是比较容易看出来的。

五、应用场景

流程固定,但流程的中方法可能不一样,这样的场景下就可以考虑使用模板方法模式。

六、结语

欢迎批评指正!


结束!

相关推荐
咖啡八杯1 天前
GoF设计模式——备忘录模式
java·后端·spring·设计模式
槑有老呆1 天前
从 Prompt Engineering 到 Harness Engineering:AI 编程的下一次跃迁
设计模式
HjhIron1 天前
从Prompt到Context:大模型应用开发的范式转移
设计模式·aigc·ai编程
咖啡八杯3 天前
GoF设计模式——中介者模式
java·后端·spring·设计模式
胡萝卜术3 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
亦暖筑序4 天前
Java 8老系统Browser Agent实战:三层拦截把AI操作后台变成可审计流程
java·后端·设计模式
青禾网络6 天前
Web 前端如何接入 AI 音效生成:从零到可用的完整方案
人工智能·设计模式
ZJPRENO7 天前
吃透软件开发六大设计原则,告别烂代码
设计模式
咖啡八杯7 天前
GoF设计模式——命令模式
java·设计模式·架构
花椒技术8 天前
HJPusher / HJPlayer SDK 实践:我们为什么把直播推播链路拆成一套可复用能力
设计模式·harmonyos·直播