【設計模式】 模式PK:抽象工廠模式VS建造者模式


1、概述

  抽象工廠模式實現對產品家族的創建,一個產品家族是這樣的一系列產品:具有不同分類維度的產品組合,采用抽象工廠模式則是不需要關心構建過程,只關心什么產品由什么工廠生產即可。而建造者模式則是要求按照指定的藍圖建造產品,它的主要目的是通過組裝零配件而產生一個新產品,兩者的區別還是比較明顯的。

  現代化的汽車工廠能夠批量生產汽車(不考慮手工打造的豪華車)。不同的工廠生產不同的汽車,寶馬工廠生產寶馬牌子的車,奔馳工廠生產奔馳牌子的車。車不僅具有不同品牌,還有不同的用途分類,如商務車Van,運動型車SUV等,我們按照兩種設計模式分別實現車輛的生產過程。

2、抽象工廠模式生產車輛

2.1 類圖

  按照抽象工廠模式,首先需要定義一個抽象的產品接口即汽車接口,然后寶馬和奔馳分別實現該接口,由於它們只具有了一個品牌屬性,還沒有定義一個具體的型號,屬於對象的抽象層次,每個具體車型由其子類實現,如R系列的奔馳車是商務車,X系列的寶馬車屬於SUV,我們來看類圖。

  在類圖中,產品類很簡單,我們從兩個維度看產品:品牌和車型,每個品牌下都有兩個車型,如寶馬SUV,寶馬商務車等,同時我們又建造了兩個工廠,一個專門生產寶馬車的寶馬工廠BMWFactory,一個是生產奔馳車的奔馳車生產工廠BenzFactory。當然,汽車工廠也有兩個不同的維度,可以建立這樣兩個工廠:一個專門生產SUV車輛的生產工廠,生產寶馬SUV和奔馳SUV,另外一個工廠專門生成商務車,分別是寶馬商務車和奔馳商務車,這樣設計在技術上是完全可行的,但是在業務上是不可行的,為什么?這是因為你看到過有一個工廠既能生產奔馳SUV也能生產寶馬SUV嗎?這是不可能的,因為業務受限,除非是國內的山寨工廠。

2.2 代碼

2.2.1 產品類

class CICar
{
public:
    CICar(){};
    ~CICar(){};
    //汽車的生產商, 也就是牌子
    virtual string msGetBand() = 0;
    virtual string msGetModel() = 0;
};

  在產品接口中我們定義了車輛有兩個可以查詢的屬性:品牌和型號,奔馳車和寶馬車是兩個不同品牌的產品,但不夠具體,只是知道它們的品牌而已,還不能夠實例化,因此還是一個抽象類。

2.2.2 抽象寶馬車

class CAbsBMW :public CICar
{
public:
    CAbsBMW() { msBand = "寶馬汽車"; }
    ~CAbsBMW(){};

    //寶馬車
    string msGetBand(){ return msBand; }
    
    // 型號由具體的實現類實現
    virtual string msGetModel() = 0;

private:
    string msBand;
};

  抽象產品類中實現了產品的類型定義,車輛的型號沒有實現,兩實現類分別實現商務車和運動型車。

2.2.3 寶馬商務車

class CBMWVan : public CAbsBMW
{
public:
    CBMWVan(){ msModel = "7系列車型商務車"; };
    ~CBMWVan(){};

    string msGetModel(){ return msModel; };

private:
    string msModel;
};

2.2.4 寶馬SUV

class CBMWSuv : public CAbsBMW
{
public:
    CBMWSuv(){ msModel = "X系列車型SUV"; };
    ~CBMWSuv(){};

    string msGetModel(){ return msModel; };

private:
    string msModel;
};

2.2.5 奔馳抽象類

  奔馳車與寶馬車類似,都已經有清晰品牌定義,但是型號還沒有確認,也是一個抽象的產品類。

class CAbsBenz :public CICar
{
public:
    CAbsBenz() { msBand = "奔馳汽車"; }
    ~CAbsBenz(){};

    //奔馳車
    string msGetBand(){ return msBand; }

    // 型號由具體的實現類實現
    virtual string msGetModel() = 0;

private:
    string msBand;
};

2.2.6 奔馳商務車

  由於分類的標准是相同的,因此奔馳車也應該有商務車和運動車兩個類型。

class CBenzVan : public CAbsBenz
{
public:
    CBenzVan(){ msModel = "R系列商務車"; };
    ~CBenzVan(){};

    string msGetModel(){ return msModel; };

private:
    string msModel;
};

2.2.7 奔馳SUV

class CBenzSuv : public CAbsBenz
{
public:
    CBenzSuv(){ msModel = "G系列SUV"; };
    ~CBenzSuv(){};

    string msGetModel(){ return msModel; };

private:
    string msModel;
};

2.2.8 抽象工廠

  所有的產品類都已經實現了,剩下的工作就是要定義工廠類進行生產,由於產品類型多樣,也導致了必須有多個工廠類來生產不同產品,首先就需要定義一個抽象工廠,聲明每個工廠必須完成的職責。

  抽象工廠定義了每個工廠必須生產兩個類型車:SUV(運動車)和VAN(商務車),否則一個工廠就不能被實例化

class CICarFactory
{
public:
    CICarFactory(){};
    ~CICarFactory(){};
    virtual CICar * mopCreateSuv() = 0;
    virtual CICar * mopCreateVan() = 0;
};

2.2.9 寶馬車工廠

class CBMWFactory : public CICarFactory
{
public:
    CBMWFactory(){};
    ~CBMWFactory(){};

    //生產SUV
    CICar * mopCreateSuv() { return new CBMWSuv; }

    //生產商務車
    CICar * mopCreateVan() { return new CBMWVan; }
};

  很簡單,你要我生產寶馬商務車,沒問題,直接產生一個寶馬商務車對象,返回給調用者,這對調用者來說根本不需要關心到底是怎么生產的,它只要找到一個寶馬工廠,即可生產出自己需要的產品(汽車)。

2.2.10 奔馳車工廠

class CBenzFactory : public CICarFactory
{
public:
    CBenzFactory(){};
    ~CBenzFactory(){};

    //生產SUV
    CICar * mopCreateSuv() { return new CBenzSuv; }

    //生產商務車
    CICar * mopCreateVan() { return new CBenzVan; }
};

2.2.11 調用場景

  產品和工廠都具備了,剩下的工作就是建立一個場景類模擬調用者調用

int main()
{
    //要求生產一輛奔馳SUV
    cout << "===要求生產一輛奔馳SUV===" << endl;
    //首先找到生產奔馳車的工廠
    cout << "A、 找到奔馳車工廠" << endl;
    CICarFactory *op_factory = new CBenzFactory;
    //開始生產奔馳SUV
    cout << "B、 開始生產奔馳SUV" << endl;
    CICar *op_benz_suv = op_factory->mopCreateSuv();
    //生產完畢, 展示一下車輛信息
    cout << "C、 生產出的汽車如下: " << endl;
    cout << "汽車品牌: " << op_benz_suv->msGetBand().c_str() << endl;
    cout << "汽車型號: " << op_benz_suv->msGetModel().c_str() << endl;

    return 0;
}

2.2.12 運行結果

2.2.13 小結

  對外界調用者來說,只要更換一個具備相同結構的對象,即可發生非常大的改變,如我們原本使用BenzFactory生產汽車,但是過了一段時間后,我們的系統需要生產寶馬汽車,這對系統來說不需要很大的改動,只要把工廠類使用BMWFactory代替即可,立刻可以生產出寶馬車,注意這里生產的是一輛完整的車,對於一個產品,只要給出產品代碼(車類型)即可生產,抽象工廠模式把一輛車認為是一個完整的、不可拆分的對象。它注重完整性,一個產品一旦找到一個工廠生產,那就是固定的型號,不會出現一個寶馬工廠生產奔馳車的情況。

3、造者模式生產車輛

  那現在的問題是我們就想要一輛混合的車型,如奔馳的引擎,寶馬的車輪,那該怎么處理呢?使用我們的建造者模式!

3.1 類圖

  按照建造者模式設計一個生產車輛需要把車輛進行拆分,拆分成引擎和車輪兩部分,然后由建造者進行建造,想要什么車,你只要有設計圖紙就成,馬上可以制造一輛車出來。它注重的是對零件的裝配、組合、封裝,它從一個細微構件裝配角度看待一個對象。我們來看生產車輛的類圖。

  注意看我們類圖中的藍圖類Blueprint,它負責對產品建造過程定義。既然要生產產品,那必然要對產品進行一個描述,在類圖中我們定義了一個接口來描述汽車。

  車輛產品描述,我們定義一輛車必須有車輪和引擎

class CICar
{
public:
    //汽車車輪
    virtual string msGetWheel() = 0;
    //汽車引擎
    virtual string msGetEngine() = 0;
};

3.2 代碼

3.2.1 具體車輛

class CCar
{
public:
    CCar(const string &sEngine, const string &sWheel)
    {
        msEngine = sEngine;
        msWheel = sWheel;
    };

    ~CCar(){};

    //汽車車輪
    string msGetWheel()  { return msWheel;  };
    //汽車引擎
    string msGetEngine() { return msEngine;  };

    string msGetInfo(){ return "車的輪子是: " + msWheel + "\n車的引擎是: " + msEngine; }

private:
    //汽車引擎
    string msEngine;
    //汽車車輪
    string msWheel;
};

  簡單定義產品的屬性,明確對產品的描述。我們繼續來思考,因為我們的產品是比較抽象的,它沒有指定引擎的型號,也沒有指定車輪的牌子,那么這樣的組合方式有很多,完全要靠建造者來建造,建造者說要生產一輛奔馳SUV那就得用奔馳的引擎和奔馳的車輪,該建造者對於一個具體的產品來說是絕對的權威,我們來描述一下建造者。

3.2.2 抽象建造者

class CCarBuilder
{
public:
    CCarBuilder() {};
    ~CCarBuilder(){};
    // 接收一份設計藍圖
    void mvSetBlueprint(CBlueprint *opBlueprint){ mopBluprint = opBlueprint; };
    CCar *mopBuildCar() { return new CCar(msBuildEngine(), msBuildWheel()); };

protected:
    // 查看藍圖, 只有真正的建造者才可以查看藍圖
    CBlueprint *mopGetBlueprint() { return mopBluprint; };
    virtual string msBuildWheel() = 0;
    virtual string msBuildEngine() = 0;

protected:
    //設計藍圖
    CBlueprint *mopBluprint;
};

  看到Blueprint類了,它中文的意思是“藍圖”,你要建造一輛車必須有一個設計樣稿或者藍圖吧,否則怎么生產?怎么裝配?該類就是一個可參考的生產樣本。

3.2.3 生產藍圖

class CBlueprint
{
public:
    string msGetWheel(){ return msWheel; }
    void mvSetWheel(const string &sWheel){ msWheel = sWheel; }

    string msGetEngine(){ return msEngine; }
    void mvSetEngine(const string &sEngine) { msEngine = sEngine; }

private:
    string msWheel;
    string msEngine;
};

  這和一個具體的產品Car類是一樣的?錯,不一樣!它是一個藍圖,是一個可以參考的模板,有一個藍圖可以設計出非常多的產品,如有一個R系統的奔馳商務車設計藍圖,我們就可以生產出一系列的奔馳車。它指導我們的產品生產,而不是一個具體的產品。我們來看寶馬車建造車間。

3.2.4 寶馬車建造車間

class CBMWBuilder : public CCarBuilder
{
public:
    CBMWBuilder(){};
    ~CBMWBuilder(){};
    string msBuildWheel() { return mopBluprint->msGetWheel(); }
    string msBuildEngine() { return mopBluprint->msGetEngine(); }
};

  這是非常簡單的類。只要獲得一個藍圖,然后按照藍圖制造引擎和車輪即可,剩下的事情就交給抽象的建造者進行裝配。奔馳車間與此類似。

3.2.5 奔馳車建造車間

class CBenzBuilder : public CCarBuilder
{
public:
    CBenzBuilder(){};
    ~CBenzBuilder(){};
    string msBuildWheel() { return mopBluprint->msGetWheel(); }
    string msBuildEngine() { return mopBluprint->msGetEngine(); }
};

  兩個建造車間都已經完成,那現在的問題就變成了怎么讓車間運作,誰來編寫藍圖?誰來協調生產車間?誰來對外提供最終產品?於是導演類出場了,它不僅僅有每個車間需要的設計藍圖,還具有指導不同車間裝配順序的職責。

3.2.6 導演類

class CDirector
{
public:
    CDirector()
    {
        mopBenzBuilder = new CBenzBuilder;
        mopBMWBuilder = new CBMWBuilder;
    }

    ~CDirector(){};

    //生產奔馳SUV
    CCar *mopCreateBenzSuv()
    {
        //制造出汽車
        return mopCreateCar(mopBenzBuilder, "benz的引擎", "benz的輪胎");
    }

    // 生產出一輛寶馬商務車
    CCar *mopCreateBMWVan()
    {
        return mopCreateCar(mopBMWBuilder, "BMW的引擎", "BMW的輪胎");
    }

    // 生產出一個混合車型
    CCar *mopCreateComplexCar()
    {
        return mopCreateCar(mopBMWBuilder, "BMW的引擎", "benz的輪胎");
    }

private:
     // 生產車輛
     CCar *mopCreateCar(CCarBuilder *opCarBuilder, const string &sEngine, const string &sWheel)
     {
        //導演懷揣藍圖
         CBlueprint *op_bp = new CBlueprint();
         op_bp->mvSetEngine(sEngine);
         op_bp->mvSetWheel(sWheel);
        opCarBuilder->mvSetBlueprint(op_bp);

        return opCarBuilder->mopBuildCar();
    }

private:
    CCarBuilder *mopBenzBuilder;
    CCarBuilder *mopBMWBuilder;
};

  這里有一個私有方法mopCreateCar,其作用是減少導演類中的方法對藍圖的依賴,全部由該方法來完成。

3.2.7 場景調用

int main()
{
    //定義出導演類
    CDirector o_director;

    //給我一輛奔馳車SUV
    cout << "===制造一輛奔馳SUV===" << endl;
    CCar *op_benz_suv = o_director.mopCreateBenzSuv();
    cout << op_benz_suv->msGetInfo().c_str() << endl;

    //給我一輛寶馬商務車
    cout << "===制造一輛寶馬商務車===" << endl;
    CCar *op_bmw_van = o_director.mopCreateBMWVan();
    cout << op_bmw_van->msGetInfo().c_str() << endl;

    //給我一輛混合車型
    cout << "===制造一輛混合車===" << endl;
    CCar *op_complex_car = o_director.mopCreateComplexCar();
    cout << op_complex_car->msGetInfo().c_str() << endl;

    return 0;
}

3.2.8 調用結果

  場景類只要找到導演類(也就是車間主任了)說給我制造一輛這樣的寶馬車,車間主任馬上通曉你的意圖,設計了一個藍圖,然后命令建造車間拼命加班加點建造,最終返回給你一件最新出品的產品,運行結果如下所示

3.2.9 小結

  注意最后一個運行結果片段,我們可以立刻生產出一輛混合車型,只要有設計藍圖,這非常容易實現。反觀我們的抽象工廠模式,它是不可能實現該功能的,因為它更關注的是整體,而不關注到底用的是奔馳引擎還是寶馬引擎,而我們的建造者模式卻可以很容易地實現該設計,市場信息變更了,我們就可以立刻跟進,生產出客戶需要的產品。

4、總結

  注意看上面的描述,我們在抽象工廠模式中使用“工廠”來描述構建者,而在建造者模式中使用“車間”來描述構建者,其實我們已經在說它們兩者的區別了,抽象工廠模式就好比是一個一個的工廠,寶馬車工廠生產寶馬SUV和寶馬VAN,奔馳車工廠生產奔馳車SUV和奔馳VAN,它是從一個更高層次去看對象的構建,具體到工廠內部還有很多的車間,如制造引擎的車間、裝配引擎的車間等,但這些都是隱藏在工廠內部的細節,對外不公布。也就是對領導者來說,他只要關心一個工廠到底是生產什么產品的,不用關心具體怎么生產。而建造者模式就不同了,它是由車間組成,不同的車間完成不同的創建和裝配任務,一個完整的汽車生產過程需要引擎制造車間、引擎裝配車間的配合才能完成,它們配合的基礎就是設計藍圖,而這個藍圖是掌握在車間主任(導演類)手中,它給建造車間什么藍圖就能生產什么產品,建造者模式更關心建造過程。雖然從外界看來一個車間還是生產車輛,但是這個車間的轉型是非常快的,只要重新設計一個藍圖,即可產生不同的產品,這有賴於建造者模式的功勞。

  相對來說,抽象工廠模式比建造者模式的尺度要大,它關注產品整體,而建造者模式關注構建過程,因此建造者模式可以很容易地構建出一個嶄新的產品,只要導演類能夠提供具體的工藝流程。也正因為如此,兩者的應用場景截然不同,如果希望屏蔽對象的創建過程,只提供一個封裝良好的對象,則可以選擇抽象工廠方法模式。而建造者模式可以用在構件的裝配方面,如通過裝配不同的組件或者相同組件的不同順序,可以產生出一個新的對象,它可以產生一個非常靈活的架構,方便地擴展和維護系統。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM