本文同時發在: http://cpper.info/2016/01/16/Five-Create-Patterns-Of-Oriented-Object.html。html
本文主要講述設計模式中的五種建立型設計模式。linux
建立型模式主要關注對象的建立過程,將對象的建立過程進行封裝,使客戶端能夠直接獲得對象,而不用去關心如何建立對象。
這裏共有5種建立型模式:編程
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。設計模式
Singleton 模式解決問題十分常見: 咱們怎樣去建立一個惟一的變量( 對象)? 好比能夠經過全局變量來解決,可是一個全局變量使得一個對象能夠被訪問,但它不能防止你實例化多個對象。網絡
一個更好的辦法是,讓類自身負責保存它的惟一實例。這個類能夠保證沒有其餘實例能夠被建立(經過截取建立新對象的請求),而且它能夠提供一個訪問該實例的方法。這就是Singleton模式。數據結構
template <class T> class Singleton { public: static T* getInstancePtr() { if(0 == proxy_.instance_) { createInstance(); } return proxy_.instance_; } static T& getInstanceRef() { if(0 == proxy_.instance_) { createInstance(); } return *(proxy_.instance_); } static T* createInstance() { return proxy_.createInstance(); } static void deleteInstance() { proxy_.deleteInstance(); } private: struct Proxy { Proxy() : instance_(0) { } ~Proxy() { if(instance_) { delete instance_; instance_ = 0; } } T* createInstance() { T *p = instance_; if(p == 0) { zl::thread::LockGuard<zl::thread::Mutex> guard(lock_); if((p = instance_) ==0) { instance_ = p = new T; } } return instance_; } void deleteInstance() { if(proxy_.instance_) { delete proxy_.instance_; proxy_.instance_ = 0; } } T *instance_; zl::thread::Mutex lock_; }; protected: Singleton() { } ~Singleton() { } private: static Proxy proxy_; }; // usage class SomeMustBeOneObject : private Singleton<SomeMustBeOneObject> {} SomeMustBeOneObject* o1 = Singleton<SomeMustBeOneObject>::getInstancePtr(); SomeMustBeOneObject* o2 = Singleton<SomeMustBeOneObject>::getInstancePtr(); SomeMustBeOneObject& o3 = Singleton<SomeMustBeOneObject>::getInstanceRef(); assert(o1 == o2); assert(o1 == &o3); SomeMustBeOneObject* o4 = new SomeMustBeOneObject; // Compile Error! SomeMustBeOneObject o5; // Compile Error!
Singleton 算是最簡單的一個模式了,可是最初想寫對一個好用的Singleton也是有不少困難的。好比下面的這幾個實現(都有問題):框架
template <class T> class Singleton1 { public: static T* getInstancePtr() { if(instance_ == NULL) { instance_ = new T; } return T; } private: T* instance_; }; template <class T> class Singleton2 { public: static T* getInstancePtr() { LockGuard(); if(instance_ == NULL) { instance_ = new T; } return T; } private: T* instance_; }; template <class T> class Singleton3 { public: static T* getInstancePtr() { if(instance_ == NULL) { LockGuard(); instance_ = new T; } return T; } private: T* instance_; }; template <class T> class Singleton4 { public: static T* getInstancePtr() { if(instance_ == NULL) { LockGuard(); if(instance_ == NULL) { instance_ = new T; } } return T; } private: T* instance_; };
Singleton 模式是設計模式中最爲簡單、最爲常見、最容易實現,也是最應該熟悉和掌握的模式。雖然簡單,但曾經也是有很多坑的,上面Singleton一、Singleton二、Singleton3這幾個實現其實都是錯的,具體錯在哪仍是比較容易發現的,而Singleton4看似不錯,但其實也不是徹底正確的(本文最初給出的Singleton
單例模式最好的實現應該使用linux下的pthread_once或者使用C++11的std_once或者C++編譯器進行編譯。好比:模塊化
class SomeClass // 必定要使用C++11編譯器編譯 { public: SomeClass* getInstancePtr() { static SomeClass one; return &one; } private: SomeClass(); SomeClass(const SomeClass&); SomeClass& operator=(const SomeClass&); }
Singleton 模式常常和 Factory( Abstract Factory) 模式在一塊兒使用, 通常來講系統中工廠對象通常來講只須要一個。函數
通常來講有幾種狀況須要用到Factory Method:
#define TYPE_PROXY_1 1 #define TYPE_PROXY_2 2 class Proxy { public: virtual Proxy() {} int type() const { return type_; } private: int type_; }; class Proxy1 : public Proxy { public: virtual Proxy1() {} private: // some attributes }; class Proxy2 : public Proxy { public: virtual Proxy2() {} }; class ProxyFactory { public: typedef std::function<Proxy*()> CreateCallBack; typedef std::function<void(Proxy*)> DeleteCallBack; public: static void registerProxy(int type, const CreateCallBack& ccb, const DeleteCallBack& dcb) { proxyCCB_[type] = ccb; proxyDCB_[type] = dcb; } static Proxy* createProxy(int type) { auto iter = proxyCCB_.find(type); if(iter!=proxyCCB_.end()) return iter->second(); return defaultCreator(type); } static void deleteProxy(Proxy* proxy) { assert(proxy); auto iter = proxyDCB_.find(proxy->type()); if(iter!=proxyDCB_.end()) iter->second(proxy); else delete proxy; } private: static Proxy* defaultCreator(int type); static std::map<int, CreateCallBack> proxyCCB_; static std::map<int, DeleteCallBack> proxyDCB_; }; /*static*/ Proxy* ProxyFactory::defaultCreator(int type) { switch (type) { case TYPE_PROXY_1: return new Proxy1(type); case TYPE_PROXY_1: return new Proxy2(type); } return NULL; } // usage class Proxy3 : public Proxy { public: virtual Proxy3() {} static Proxy* create() { return new Proxy3; } static void destory(Proxy* p) { delete p; } }; ProxyFactory factory; factory.registerProxy(1000, &Proxy3::create, &Proxy3::destory); Proxy* p1 = factory.createProxy(TYPE_PROXY_1); Proxy* p2 = factory.createProxy(TYPE_PROXY_2); Proxy* p3 = factory.createProxy(1000);
Factory 模式在實際開發中應用很是普遍,面向對象的系統常常面臨着對象建立問題:要麼是要建立的類實在是太多了, 要麼是開始並不知道要實例化哪個類。Factory 提供的建立對象的接口封裝,能夠說部分地解決了實際問題。
固然Factory 模式也帶來一些問題, 好比沒新增一個具體的 ConcreteProduct 類,均可能要修改Factory的接口,這樣 Factory 的接口永遠就不能封閉(Close)。 這時咱們能夠經過建立一個 Factory 的子類來經過多態實現這一點,或者經過對新的ConcreteProduct向Factory註冊一個建立回調的函數。
用於建立一組相關或相互依賴的複雜對象。
好比網絡遊戲中須要過關打怪,對於不一樣等級的玩家,應該生成與此相應的怪物和場景,好比不一樣的場景,動物,等等。
又好比一個支持多種視感標準的用戶界面工具包,例如 Motif 和 Presentation Manager。不一樣的視感風格爲諸如滾動條、窗口和按鈕等用戶界面「窗口組件」定義不一樣的外觀和行爲。爲保證視感風格標準間的可移植性,一個應用不該該爲一個特定的視感外觀硬編碼它的窗口組件。在整個應用中實例化特定視感風格的窗口組件類將使得之後很難改變視感風格。
class AbstractProductA { public: virtual ~AbstractProductA(); }; class AbstractProductB { public: virtual ~AbstractProductB(); }; class ProductA1: public AbstractProductA { }; class ProductA2: public AbstractProductA { }; class ProductB1: public AbstractProductB { }; class ProductB2: public AbstractProductB { }; class AbstractFactory { public: virtual ~AbstractFactory(); virtual AbstractProductA *CreateProductA() = 0; virtual AbstractProductB *CreateProductB() = 0; }; class ConcreteFactory1: public AbstractFactory { public: AbstractProductA *CreateProductA() { return new ProductA1; } AbstractProductB *CreateProductB() { return new ProductB1; } }; class ConcreteFactory2: public AbstractFactory { public: AbstractProductA *CreateProductA() { return new ProductA2; } AbstractProductB *CreateProductB() { return new ProductB2; } }; //usage int main(int argc, char *argv[]) { AbstractFactory *cf1 = new ConcreteFactory1(); cf1->CreateProductA(); cf1->CreateProductB(); AbstractFactory *cf2 = new ConcreteFactory2(); cf2->CreateProductA(); cf2->CreateProductB(); }
在如下狀況可使用 Abstract Factory模式
Abstract Factory 模式和 Factory 模式二者比較類似,可是仍是有區別的,AbstractFactory 模式是爲建立一組(有多種不一樣類型)相關或依賴的對象提供建立接口, 而 Factory 模式是爲一類對象提供建立接口或延遲對象的建立到子類中實現。而且Abstract Factory 模式一般都是使用 Factory 模式實現的。
將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。
對於一個大型的、複雜對象,咱們但願將該對象的建立過程與其自己的表示或數據結構相分離。
當咱們要建立的對象很複雜的時候(一般是由不少其餘的對象組合而成),咱們要將複雜對象的建立過程和這個對象的表示(展現)分離開來, 這樣作的好處就是經過一步步的進行復雜對象的構建, 因爲在每一步的構造過程當中能夠引入參數,使得通過相同的步驟建立最後獲得的對象的展現不同。
class Builder { public: virtual ~Builder() {} virtual void BuildPartA(const string &buildPara) = 0; virtual void BuildPartB(const string &buildPara) = 0; virtual void BuildPartC(const string &buildPara) = 0; virtual Product *GetProduct() = 0; }; class ConcreteBuilder: public Builder { public: void BuildPartA(const string &buildPara) { cout << "Step1:Build PartA..." << buildPara << endl; } void BuildPartB(const string &buildPara) { cout << "Step1:Build PartB..." << buildPara << endl; } void BuildPartC(const string &buildPara) { cout << "Step1:Build PartC..." << buildPara << endl; } Product *GetProduct() { BuildPartA("pre-defined"); BuildPartB("pre-defined"); BuildPartC("pre-defined"); return new Product(); } }; class Director { public: Director(Builder *bld) { _bld = bld ; } void Construct() { _bld->BuildPartA("user-defined"); _bld->BuildPartB("user-defined"); _bld->BuildPartC("user-defined"); } private: Builder *_bld; }; // usage Director *d = new Director(new ConcreteBuilder()); d->Construct();
另一個例子,好比Java語言中的StringBuilder類(聽說用來構造字符串對象時很是高效,相比String類):
StringBuilder MyStringBuilder = new StringBuilder("Your total is "); MyStringBuilder.AppendFormat("{0:C} ", MyInt); Console.WriteLine(MyStringBuilder); MyStringBuilder.Insert(6,"Beautiful "); MyStringBuilder.Remove(5,7);
Builder 模式經過一步步建立對象,並經過相同的建立過程能夠得到不一樣的結果對象(每一步的建立過程均可以綁定不一樣的參數)
用原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象, 也即經過一個已存在對象來進行新對象的建立。
你能夠經過定製一個通用的圖形編輯器框架和增長一些表示音符、休止符和五線譜的新對象來構造一個曲譜編輯器。這個編輯器框架可能有一個工具選擇板用於將這些音樂對象加到曲譜中。這個選擇板可能還包括選擇、移動和其餘操縱音樂對象的工具。用戶能夠點擊四分音符工具並使用它將四分音符加到曲譜中。或者他們可使用移動工具在五線譜上上下移動一個音符,從而改變它的音調。
咱們假定該框架爲音符和五線譜這樣的圖形構件提供了一個抽象的Graphics類。此外,爲定義選擇板中的那些工具,還提供一個抽象類Tool。該框架還爲一些建立圖形對象實例並將它們加入到文檔中的工具預約義了一個GraphicsTool子類。但GraphicsTool給框架設計者帶來一個問題。音符和五線譜的類特定於咱們的應用,而GraphicsTool類卻屬於框架。GraphicsTool不知道如何建立咱們的音樂類的實例,並將它們添加到曲譜中。咱們能夠爲每一種音樂對象建立一個GraphicsTool的子類,但這樣會產生大量的子
類,這些子類僅僅在它們所初始化的音樂對象的類別上有所不一樣。咱們知道對象複合是比建立子類更靈活的一種選擇。問題是,該框架怎麼樣用它來參數化GraphicsTool的實例,而這些實例是由Graphics類所支持建立的。
解決辦法是讓GraphicsTool經過拷貝或者「克隆」一個Graphics子類的實例來建立新的Graphics,咱們稱這個實例爲一個原型。GraphicsTool將它應該克隆和添加到文檔中的原型做爲參數。若是全部Graphics子類都支持一個Clone操做,那麼GraphicsTool能夠克隆全部種類的Graphics。
所以在咱們的音樂編輯器中,用於建立個音樂對象的每一種工具都是一個用不一樣原型進行初始化的GraphicsTool實例。經過克隆一個音樂對象的原型並將這個克隆添加到曲譜中,每一個GraphicsTool實例都會產生一個音樂對象。
class Prototype { public: virtual Prototype *Clone() const = 0; }; class ConcretePrototype: public Prototype { public: Prototype *Clone() const { return new ConcretePrototype(*this); } }; //use Prototype *p = new ConcretePrototype(); Prototype *p1 = p->Clone(); Prototype *p2 = p->Clone();
Prototype 模式經過複製原型(Prototype)而得到新對象建立的功能,這裏 Prototype 自己就是「對象工廠」(由於可以生產對象)。
並且Prototype和Factory仍是很類似的, Factory是由外部類負責產品的建立,而Prototype是由類自身負責產品的建立。
首先,在編程中,對象的建立一般是一件比較複雜的事,由於,爲了達到下降耦合的目的,咱們一般採用面向抽象編程的方式,對象間的關係不會硬編碼到類中,而是等到調用的時候再進行組裝,這樣雖然下降了對象間的耦合,提升了對象複用的可能,但在必定程度上將組裝類的任務都交給了最終調用的客戶端程序,大大增長了客戶端程序的複雜度。採用建立類模式的優勢之一就是將組裝對象的過程封裝到一個單獨的類中,這樣,既不會增長對象間的耦合,又能夠最大限度的減少客戶端的負擔。
其次,使用普通的方式建立對象,通常都是返回一個具體的對象,即所謂的面向實現編程,這與設計模式原則是相違背的。採用建立類模式則能夠實現面向抽象編程。客戶端要求的只是一個抽象的類型,具體返回什麼樣的對象,由建立者來決定。
再次,能夠對建立對象的過程進行優化,客戶端關注的只是獲得對象,對對象的建立過程則不關心,所以,建立者能夠對建立的過程進行優化,例如在特定條件下,若是使用單例模式或者是使用原型模式,均可以優化系統的性能。
全部的建立類模式本質上都是對對象的建立過程進行封裝。
建立型模式的目標都是相同的,即負責產品對象的建立。其中Singleton是使某一產品只有一個實例,Factory Method負責某一產品(及其子類)的建立,Abstract Factory負責某一系列相關或相互依賴產品(及相應子類)的建立,Builder模式經過一些複雜協議或者複雜步驟建立某一產品,Prototype則是經過複製自身來建立新對象。
一般來講Abstract Factory能夠經過Factory來實現,且通常都是Singleton模式。
着重推薦Singleton、Factory、Abstract Factory模式,而Builder和Prototype目前我直接使用的還不多。
接下來來模擬一個虛擬場景來演示這5種建立型模式的組合使用。假設有一個關於車輛的組裝系統,目前有汽車(Car)和貨車(Truck),每種車由發動機和車輪子構成(忽略其餘零件),且可能有不一樣的發動機和車輪子,好比汽車的發動機和輪子與貨車的就不同,甚至不一樣類型的汽車的發動機和輪子也可能不徹底同樣。我將在該場景中同時使用這五種模式,以研究各模式的使用場景以及相互間的配合。
請等待下文介紹, 連接在此。