【设计模式

在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。例如,计算机是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的;游戏中的角色,也是有性别、个性、能力、脸型、体型、服装、发型等多个特性。

以上这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。


一、定义与特点

建造者(Builder)模式的定义:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

该模式的主要优点如下:

  • 封装性好,构建和表示分离。
  • 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
  • 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。

其缺点如下:

  • 产品的组成部分必须相同,这限制了其使用范围。
  • 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
  • 产品越多,需要的具体建造者类也就越多。

建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。


二、对建造者模式的理解

建造者模式的定义看着让人迷惑,什么叫构建和表示的分离?一个对象使用构造函数构造之后不就固定了,只能通过它的方法来改变它的属性,而且还要同样的构建过程搞出不同的表示,怎么可能呢?多写几个构造函数?

其实多写几个构造函数,根据不同参数设置对象不同的属性,也可以达到这样的效果,只是这样就非常麻烦了,每次要增加一种表示就要添加一个构造函数,将来构造函数会多得连自己都不记得了,这违背了开放-封闭的原则。

要不就只能多设计几个 set 函数,每次属性不一样了,就用 set 函数改变对象的属性。这样也可以达到效果。只是代码就会非常冗余了,每个要用到这个对象的地方,都要写上好几句语句,一旦对象有点什么变化,还得到处都改一遍,这样就很容易出错,而且这也违背了依赖倒转的原则。就像下面这样:

#include <iostream>

using namespace std;

// 手机产品类
class Phone
{
public:
    Phone() {}
    ~Phone() {}

    // 方式1:多写几个构造函数,根据不同参数设置对象不同的属性
    Phone(string mainboard, string battery, string screen) {
        m_mainboard = mainboard;
        m_battery = battery;
        m_screen = screen;
    }
    // 方式2:多设计几个set函数,每次属性不一样了,就用set函数改变对象的属性
    void setMainboard(string mainboard) {m_mainboard = mainboard;}
    void setBattery(string battery) {m_battery = battery;}
    void setScreen(string screen) {m_screen = screen;}

    void displayMainboard() {cout << m_mainboard << endl;}
    void displayBattery() {cout << m_battery << endl;}
    void displayScreen() {cout << m_screen << endl;}

private:
    string m_mainboard;
    string m_battery;
    string m_screen;
};

int main()
{
    // 方式1:多写几个构造函数,根据不同参数设置对象不同的属性
    Phone* xiaomiPhone = new Phone("GaoTong", "Other", "Samsung");
    // 显示其信息
    xiaomiPhone->displayMainboard();
    xiaomiPhone->displayBattery();
    xiaomiPhone->displayScreen();

    // 方式2:多设计几个set函数,每次属性不一样了,就用set函数改变对象的属性
    Phone* applePhone = new Phone;
    applePhone->setMainboard("A13");
    applePhone->setBattery("apple");
    applePhone->setScreen("Samsung");
    // 显示其信息
    applePhone->displayMainboard();
    applePhone->displayBattery();
    applePhone->displayScreen();

    // 销毁指针
    delete xiaomiPhone;
    xiaomiPhone = NULL;

    getchar();
}

输出如下:

GaoTong
Other
Samsung
A13
apple
Samsung

不能加很多构造函数,也不能直接用一堆 set 函数,然后发现,有些对象的构建是固定的几个步骤的,就像一条流水线一样,任何的产品都是通过每一个固定的步骤拼凑出来的。例如说一部手机,先放主板,再放屏幕,再放电池,再放外壳就能卖几千了,每次推出新产品,就换个更好的主板,换个大点的屏幕,再整个大容量电池,又能卖出个新价钱。就是说,这些步骤都没有变,变的只是每个部分的东西。

这就是大神的厉害之处了,透过现象看本质,基本有变的,有不变的,那敢情好,面向对象的一个重要指导思想就是,封装隔离变化的,留出不变的。于是他们就用一个 Builder 类把步骤中的每个部分封装起来,这个类的主要作用就是生产每个部件,再抽象一下提升高度,这样就依赖倒转了,这样每次只需要添加一个类,这个类还是这几个部分,只是内部的实现已经不一样了,这样就满足了开放-封闭的原则了。

但还是有一个问题,光有 Builder 类还不行,虽然产品的每个部分都有对应的函数,但是用起来的话,还是跟前面说的 set 函数一样,一用就要使用一大堆函数,也就是这变的东西是封装起来了,但这不变的东西还没留出来。这时,就添加一个 Director 类,这个类就是专门规定组装产品的步骤的,这样只要告诉 Director 使用哪个 Builder,就能生产出不同的产品,对于客户端来说,只看到用了 Director 的一个 construct 函数,甚是方便。

再反过来看建造者模式的定义,构建指的就是生产一个产品的步骤,表示就是每个产品部分的具体实现,通过 Director 封装步骤,通过 Builder 封装产品部分的实现,再把他两隔离开,就能隔离变的,留出不变的供客户端使用。


三、模式的结构

建造者模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。主要角色如下。

  • 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
  • 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
  • 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  • 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

其结构图如下图所示。

建造者模式的结构图


四、模式的实现

上图给出了建造者模式的主要结构,其相关类的代码如下:

(1) 产品角色:包含多个组成部件的复杂对象。

class Product {
public:
    void setPartA(string partA) {m_partA = partA;}
    void setPartB(string partB) {m_partB = partB;}
    void setPartC(string partC) {m_partC = partC;}

    void show() {
        //显示产品的特性
        cout << "m_partA: " << m_partA << endl;
        cout << "m_partB: " << m_partB << endl;
        cout << "m_partC: " << m_partC << endl;
    }

private:
    string m_partA;
    string m_partB;
    string m_partC;
};

(2) 抽象建造者:包含创建产品各个子部件的抽象方法。

class Builder {
protected:
    Product *m_product; //创建产品对象

public:
    Builder() {}
    virtual void buildPartA() = 0;
    virtual void buildPartB() = 0;
    virtual void buildPartC() = 0;

    //返回产品对象
    Product *getResult() {return m_product;}
};

(3) 具体建造者:实现了抽象建造者接口。

class ConcreteBuilder : public Builder {
public:
    ConcreteBuilder() {m_product = new Product();}
    void buildPartA() {m_product->setPartA("PartA");}
    void buildPartB() {m_product->setPartB("PartB");}
    void buildPartC() {m_product->setPartC("PartC");}
};

(4) 指挥者:调用建造者中的方法完成复杂对象的创建。

class Director {
private:
    Builder *m_builder;

public:
    Director(Builder *builder) {m_builder = builder;}

    //产品构建与组装方法
    Product *construct() {
        m_builder->buildPartA();
        m_builder->buildPartB();
        m_builder->buildPartC();
        return m_builder->getResult();
    }
};

(5) 客户类。

int main()
{
    Builder *builder = new ConcreteBuilder();
    Director *director = new Director(builder);
    Product *product = director->construct();
    product->show();

    getchar();
}

输出如下:

m_partA: PartA
m_partB: PartB
m_partC: PartC

与工厂模式不同,建造者模式是在 Director 的控制下一步一步构造产品的。建造小人就是在 Builder 控制下一步步构造出来的。创建者模式可以能更精细的控制构建过程,从而能更精细的控制所得产品的内部结构。


五、应用实例

手机有很多,例如小米和苹果,虽然名称不一样,但是内部结构基本差不多,都是先放主板,再放电池,再放屏幕。这种场景就很适合使用建造者模式:

#include <iostream>

using namespace std;

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

// 手机产品类
class Phone
{
public:
    Phone() {}
    ~Phone() {}
    void setMainboard(string mainboard) {m_mainboard = mainboard;}
    void setBattery(string battery) {m_battery = battery;}
    void setScreen(string screen) {m_screen = screen;}
    void displayMainboard() {cout << m_mainboard << endl;}
    void displayBattery() {cout << m_battery << endl;}
    void displayScreen() {cout << m_screen << endl;}

private:
    string m_mainboard;
    string m_battery;
    string m_screen;
};

// 抽象建造者类
class Builder
{
public:
    Builder() {}
    ~Builder() {}

protected:
    Phone *m_phone;

public:
    virtual void buildMainboard() = 0;
    virtual void buildBattery() = 0;
    virtual void buildScreen() = 0;
    virtual Phone* getPhone() {return m_phone;}
};

// 小米建造者类
class XiaomiBuilder : public Builder
{
public:
    XiaomiBuilder() {m_phone = new Phone;}
    void buildMainboard() {m_phone->setMainboard("GaoTong");}
    void buildBattery() {m_phone->setBattery("Other");}
    void buildScreen() {m_phone->setScreen("Samsung");}
};

// 苹果建造者类
class AppleBuilder :public Builder
{
public:
    AppleBuilder() {m_phone = new Phone;}
    void buildMainboard() {m_phone->setMainboard("A13");}
    void buildBattery() {m_phone->setBattery("apple");}
    void buildScreen() {m_phone->setScreen("Samsung");}
};

// 指挥者类
class Director
{
public:
    Director() {}
    ~Director() {}
    Phone* Construct(Builder* builedr) {
        builedr->buildScreen();
        builedr->buildBattery();
        builedr->buildMainboard();
        return builedr->getPhone();
    }
};

int main()
{
    // 指挥者
    Director *director = new Director;

    // 构造小米手机产品,并获取小米手机对象
    Builder *xiaomi = new XiaomiBuilder;
    Phone* xiaomiPhone = director->Construct(xiaomi);
    // 显示其信息
    xiaomiPhone->displayMainboard();
    xiaomiPhone->displayBattery();
    xiaomiPhone->displayScreen();

    // 构造苹果手机产品,并获取苹果手机对象
    Builder* apple = new AppleBuilder;
    Phone* applePhone = director->Construct(apple);
    // 显示其信息
    applePhone->displayMainboard();
    applePhone->displayBattery();
    applePhone->displayScreen();

    // 销毁指针
    SAFE_DELETE(director);
    SAFE_DELETE(xiaomi);
    SAFE_DELETE(xiaomiPhone);
    SAFE_DELETE(apple);
    SAFE_DELETE(applePhone);

    getchar();
}


六、应用场景

建造者模式唯一区别于工厂模式的是针对复杂对象的创建。也就是说,如果创建简单对象,通常都是使用工厂模式进行创建,而如果创建复杂对象,就可以考虑使用建造者模式。

当需要创建的产品具备复杂创建过程时,可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,使创建产品的灵活性大大增加。

建造者模式主要适用于以下应用场景:

  • 相同的方法,不同的执行顺序,产生不同的结果。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
  • 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
  • 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。

七、建造者模式和工厂模式的区别

通过前面的学习,我们已经了解了建造者模式,那么它和工厂模式有什么区别呢?

  • 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
  • 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样。
  • 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
  • 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。

参考:

建造者模式及C++实现

c++设计模式之建造者模式

设计模式C++实现(6)——建造者模式

菜鸟教程-建造者模式(Bulider模式)详解


原文地址:https://www.cnblogs.com/linuxAndMcu/p/12434373.html