zoukankan      html  css  js  c++  java
  • 【设计模式

    在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。例如,计算机是由 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模式)详解


  • 相关阅读:
    iOS——归档对象的创建,数据写入与读取
    iOS——plist的创建,数据写入与读取
    SQL SERVER 2005快捷键
    图片放大源码
    验证url 地址是否是图片
    JS三大经典变量命名法
    载入锁频
    SQL Server 查询分析器键盘快捷方式
    关于ajax get方式请求 url地址参数怎么变成空了的问题
    SQL计算表的列数
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/12434373.html
Copyright © 2011-2022 走看看