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,它是從一個更高層次去看對象的構建,具體到工廠內部還有很多的車間,如制造引擎的車間、裝配引擎的車間等,但這些都是隱藏在工廠內部的細節,對外不公布。也就是對領導者來說,他只要關心一個工廠到底是生產什么產品的,不用關心具體怎么生產。而建造者模式就不同了,它是由車間組成,不同的車間完成不同的創建和裝配任務,一個完整的汽車生產過程需要引擎制造車間、引擎裝配車間的配合才能完成,它們配合的基礎就是設計藍圖,而這個藍圖是掌握在車間主任(導演類)手中,它給建造車間什么藍圖就能生產什么產品,建造者模式更關心建造過程。雖然從外界看來一個車間還是生產車輛,但是這個車間的轉型是非常快的,只要重新設計一個藍圖,即可產生不同的產品,這有賴於建造者模式的功勞。
相對來說,抽象工廠模式比建造者模式的尺度要大,它關注產品整體,而建造者模式關注構建過程,因此建造者模式可以很容易地構建出一個嶄新的產品,只要導演類能夠提供具體的工藝流程。也正因為如此,兩者的應用場景截然不同,如果希望屏蔽對象的創建過程,只提供一個封裝良好的對象,則可以選擇抽象工廠方法模式。而建造者模式可以用在構件的裝配方面,如通過裝配不同的組件或者相同組件的不同順序,可以產生出一個新的對象,它可以產生一個非常靈活的架構,方便地擴展和維護系統。