设计模式:模板方法模式

目录

一、引言

二、优化前的代码

三、模板方法模式

四、优化后的代码

五、应用场景

六、结语


一、引言

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

冲饮料的固定流程:

  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. 单一职责:只负责制作饮料。

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

五、应用场景

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

六、结语

欢迎批评指正!


结束!

相关推荐
乐观的山里娃2 小时前
【设计模式 12】原型:复制成功
设计模式
傻啦嘿哟2 小时前
办公Agent与人工审核的“握手协议”:关键操作二次确认的设计模式
设计模式
hssfscv3 小时前
软件设计师2021上、下上午题错题解析+2022上、下下午题训练5道 练习真题训练16
笔记·设计模式·uml
乐观的山里娃4 小时前
【设计模式 13】命令:覆水能收
设计模式
乐观的山里娃5 小时前
【设计模式 11】建造者:配置像天书
设计模式
看山是山_Lau1 天前
建造者模式:复杂对象如何一步步构建
设计模式·建造者模式
霸道流氓气质1 天前
业务链路追踪日志设计模式 — 从原理到实践
设计模式
nnsix2 天前
设计模式 - 建造者模式 笔记
笔记·设计模式·建造者模式
cui17875682 天前
矩阵拼团 + 复购拼团:新零售最稳的复购模式,规则简单
大数据·人工智能·设计模式·零售
百珏2 天前
[灰度发布]:全链路透传组件:APM、自研方案与 Java Agent 的实现取舍
后端·设计模式·架构