本文同時發在: http://cpper.info/2016/01/16/Five-Create-Patterns-Of-Oriented-Object.html。
本文主要講述設計模式中的五種創建型設計模式。
創建型模式
創建型模式主要關注對象的創建過程,將對象的創建過程進行封裝,使客戶端可以直接得到對象,而不用去關心如何創建對象。
這里共有5種創建型模式:
- 單例模式(Singleton) : 用於得到某類型的唯一對象;
- 工廠方法模式(Factory Method) : 用於創建復雜對象;
- 抽象工廠模式(Abstract Factory) : 用於創建一組相關或相互依賴的復雜對象;
- 建造者模式(Builder) : 用於創建模塊化的更加復雜的對象;
- 原型模式(ProtoType) : 用於得到一個對象的拷貝;
1. 單例模式(Singleton)
意圖
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
問題
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 模式是設計模式中最為簡單、最為常見、最容易實現,也是最應該熟悉和掌握的模式。雖然簡單,但曾經也是有不少坑的,上面Singleton1、Singleton2、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) 模式在一起使用, 一般來說系統中工廠對象一般來說只需要一個。
2. 工廠方法模式(Factory Method)
意圖
- 定義一個用於創建對象的接口,讓子類決定實例化哪一個類;
- 使一個類的實例化延遲到其子類。
問題
一般來說有幾種情況需要用到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注冊一個創建回調的函數。
3. 抽象工廠模式(Abstract 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 模式實現的。
4. 建造者模式(Builder)
意圖
將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
問題
對於一個大型的、復雜對象,我們希望將該對象的創建過程與其本身的表示或數據結構相分離。
當我們要創建的對象很復雜的時候(通常是由很多其他的對象組合而成),我們要將復雜對象的創建過程和這個對象的表示(展示)分離開來, 這樣做的好處就是通過一步步的進行復雜對象的構建, 由於在每一步的構造過程中可以引入參數,使得經過相同的步驟創建最后得到的對象的展示不一樣。
示例
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 模式通過一步步創建對象,並通過相同的創建過程可以獲得不同的結果對象(每一步的創建過程都可以綁定不同的參數)
5. 原型模式(ProtoType)
意圖
用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象, 也即通過一個已存在對象來進行新對象的創建。
問題
你可以通過定制一個通用的圖形編輯器框架和增加一些表示音符、休止符和五線譜的新對象來構造一個樂譜編輯器。這個編輯器框架可能有一個工具選擇板用於將這些音樂對象加到樂譜中。這個選擇板可能還包括選擇、移動和其他操縱音樂對象的工具。用戶可以點擊四分音符工具並使用它將四分音符加到樂譜中。或者他們可以使用移動工具在五線譜上上下移動一個音符,從而改變它的音調。
我們假定該框架為音符和五線譜這樣的圖形構件提供了一個抽象的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),每種車由發動機和車輪子構成(忽略其他零件),且可能有不同的發動機和車輪子,比如汽車的發動機和輪子與貨車的就不一樣,甚至不同類型的汽車的發動機和輪子也可能不完全一樣。我將在該場景中同時使用這五種模式,以研究各模式的使用場景以及相互間的配合。
請等待下文介紹, 鏈接在此。