天天看點

面向對象設計模式之五種建立型模式

本文介紹了設計模式中的五種建立型設計模式,分别是單例模式、工廠方法模式、抽象工廠模式、建造者模式、原型模式,本文參考自GOF名著

本文同時發在: 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與Singleton4是一個思路),具體問題請搜尋DCL(double check lock)。

單例模式最好的實作應該使用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)

  1. 定義一個用于建立對象的接口,讓子類決定執行個體化哪一個類;
  2. 使一個類的執行個體化延遲到其子類。

一般來說有幾種情況需要用到Factory Method:

  1. 一套工具庫或者架構實作并沒有考慮業務相關的東西,在我們實作自己的業務邏輯時,可能需要注冊我們自己的業務類型到架構中;
  2. 面向對象中,在有繼承關系的體系下,可能給最初并不知道要建立何種類型,需要在特定時機下動态建立;

#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),每種車由發動機和車輪子構成(忽略其他零件),且可能有不同的發動機和車輪子,比如汽車的發動機和輪子與貨車的就不一樣,甚至不同類型的汽車的發動機和輪子也可能不完全一樣。我将在該場景中同時使用這五種模式,以研究各模式的使用場景以及互相間的配合。

請等待下文介紹, 連結在此。

繼續閱讀