建造者模式

来来来,换个建造者模式走一走

意图

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

UML

先看 UML 图大致了解一下代码的框架

UML

  • Builder (抽象建造者)
    为创建一个 Product 对象的各个部件指定抽象接口
  • ConcreteBuilder (具体建造者)
    • 实现 Builder 的接口以构造和装配改产品的各个部件
    • 定义并明确它所创建的表示
    • 提供一个检索产品的接口
  • Director (指挥者)
    构造一个使用Builder接口的对象
  • Product (产品)
    • 表示被构造的复杂对象, ConcreteBuilder创建改产品的内部表示并定义它的装配过程
    • 包含定义组成部件的类,包括将这些部件装配成最终产品的接口

看了UML图,是不是很困惑作为客户端应该如何调用,再来:

时序图

  • 客户端创建它想要的产品 Builder 对象
  • 客户端创建指挥的类 Director
  • 利用 Direcotor 指挥 ConcreteBuilder 构建 Product
  • 最后从 ConcreteBuilder 获取 Product

优缺点

优点

  • 它使你可以改变一个产品的内部表示
    因为产品时通过 Director 调用抽象的接口来进行构造的,所以隐藏了这个产品的表示和内部的结构,同时也隐藏了这个产品是如何装配的
    当我需要改变产品的内部表示的时候,只需要重新定义一个 Director

  • 它将构造代码和表示代码分开
    封装了复杂对象的创建和表示方式,提过了对象的封装性

  • 对构造过程进行更精细的控制
    建造者模式接口相比其他创建型模式能更好的反映产品的构造过程,因此可以更精细的控制构造过程,从而能更精细的控制所得的产品内部结构

缺点

  • 产品在构造过程中必须有相同的构造过程,应用范围就小很多
  • 如果产品内部变化复杂,可能会需要使用很多具体建造者

应用场景

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时
  • 当构造过程必须允许被构造的对象有不同的表示时

代码实现

趁着双十一打折,可能有一部分开始自己组装电脑,而一台电脑无非以下几个部分:CPU,主板,内存,显卡,存储,电源,散热,机箱,有这8部分,就可以组装好一台电脑
各个配件的档次不同,性能也不同,配出电脑也不同, 所以电脑的种类可能就是无限的
根据从 B站装机猿 那里看到的一些电脑配置推荐,现在有 6666 和 11199 2个价位电脑

用建造者模式设计上面这个例子的代码如下:

先是电脑这个产品类,主要就是记录每个部件的名称,以及价格信息,在调用 show() 时,将它所有的信息打印出来

#include <string>
#include <list>
#include <istream>

class Computer
{
public:
    void add(std::string part) { m_module.push_back(part);}
    void setPrice(int price) {m_price = price;}

    void show() {
        std::cout << m_price << " 价格的配置电脑如下: "<< std::endl;
        for (auto moudle : m_module)
            std::cout << moudle << std::endl;
    }

private:
    std::list<std::string> m_module;
    int m_price;
};

OK,接下来就是建造电脑的类了,这里推荐2个档次的, 6666 价位以及 11199 价位

#include "computer.h"

class Builder {
public:
    virtual void BuildComputer() {}
    virtual void AddCpu(){}
    virtual void AddMainboard(){}
    virtual void AddMemory(){}
    virtual void AddGpu() {}
    virtual void AddStorage(){}
    virtual void AddPowersupply(){}
    virtual void AddRadiator() {}
    virtual void AddCase(){}
    virtual Computer *GetComputer() { return nullptr;}
protected:
    Builder(){}
};

class Builder6666: public Builder
{
public:
    Builder6666() : m_computer(nullptr) {}
    virtual void BuildComputer() {  m_computer = new Computer; m_computer->setPrice(6666);}
    virtual void AddCpu() { m_computer->add("CPU:10400F");}
    virtual void AddMainboard() {m_computer->add("主板:微星B460M PRO VDH WIFI");}
    virtual void AddMemory() {m_computer->add("内存:金士顿骇客神条2666 8Gx2");}
    virtual void AddGpu() {m_computer->add("显卡:微星万图师3X RTX3070");}
    virtual void AddStorage() {m_computer->add("固态:西数SN550 500G");}
    virtual void AddPowersupply() {m_computer->add("电源:九州风神DQ650-M-V2L");}
    virtual void AddRadiator() {m_computer->add("散热器:九州风神玄冰400蓝光版");}
    virtual void AddCase() {m_computer->add("机箱:先马平头哥M3");}
    virtual Computer *GetComputer() { return m_computer; }

private:
    Computer *m_computer;
};

class Builder11199: public Builder
{
public:
    Builder11199() : m_computer(nullptr){}
    virtual void BuildComputer() {  m_computer = new Computer; m_computer->setPrice(11199);}
    virtual void AddCpu() { m_computer->add("CPU:AMD锐龙3900X散片");}
    virtual void AddMainboard() {m_computer->add("主板:微星B550 CARBON WIFI");}
    virtual void AddMemory() {m_computer->add("内存:金士顿骇客神条RGB3200 8Gx2");}
    virtual void AddGpu() {m_computer->add("显卡:微星RTX2070Super魔龙");}
    virtual void AddStorage() {m_computer->add("固态:西数黑盘SN750 500G");}
    virtual void AddPowersupply() {m_computer->add("电源:九州风神DQ850");}
    virtual void AddRadiator() {m_computer->add("散热器:九州风神堡垒360EX RGB");}
    virtual void AddCase() {m_computer->add("机箱:九州风神CL500");}
    virtual Computer *GetComputer() { return m_computer; }

private:
    Computer *m_computer;
};

造电脑的类 Builder 相关的类完成了,只剩下指挥的 Director 类了

#include "builder.h"

class Director
{
public:
    void BuildUp(Builder& builder)
    {
        builder.BuildComputer();
        builder.AddCpu();
        builder.AddMainboard();
        builder.AddMemory();
        builder.AddGpu();
        builder.AddStorage();
        builder.AddPowersupply();
        builder.AddRadiator();
        builder.AddCase();
    }
};

最后就是 main() 部分的调用了

#include <iostream>
#include "director.h"

using namespace std;

#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if(p){ delete p; p=nullptr;} }
#endif

int main()
{
    Director director;

    Builder6666 build6666;
    Builder11199 build11199;

    director.BuildUp(build6666);
    director.BuildUp(build11199);

    Computer *computer6666 = build6666.GetComputer();
    Computer *computer11199 = build11199.GetComputer();

    computer6666->show();
    computer11199->show();

    SAFE_DELETE(computer6666);
    SAFE_DELETE(computer11199);

    return 0;
}

运行结果如下图:

运行结果

总结

产品类没有抽象的原因:

通常情况下,客户端都是通过具体建造者来获取产品,所以客户端的位置让它知道 Builder 的哪一个具体子类对应的产品是什么,也能相应的处理它的产品。所以我觉得建造者模式核心还是同样的构建过程,最后用这个构建过程出什么产品,就由具体的建造者决定了

建造者模式在我眼里实际上是就是对 复杂的相同的构造过程 进行的封装,而这个封装就是指挥者 DirectorDirector 可以自由定义每部分的初始化调用的顺序,是否调用等等操作,而每部分如何初始化,抽象成 Builder, 具体建造者继承 Builder 只需要安心的实现每部分的初始化的代码,所以很好的将一个复杂对象的构建与它的表示分离

代码的例子是组装一台完整的电脑,可能有时候只需要组装 主板+CPU,这时候对于 Director 而言,只要添加一个新的接口,只调用 Builder 的构造主板和CPU的函数即可,这时候,从 Builder 中获取的电脑就只有主板和CPU,也说明了将对象的构建与它的表示分离的设计在扩展的时候也是很简单的