1. 程式人生 > >設計模式的一點總結和思考(一)建立型

設計模式的一點總結和思考(一)建立型

面向介面程式設計

對於當前不知道或無法確定的東西,我們就抽象它,只對其介面操作,即現在不知道具體的涉及物件,但我知道如何使用它,先用其介面,待以後知道了具體的物件之後,再繫結上即可,這就是所謂的封裝變化。

雖然不確定目標是誰,但可以確定如何使用目標。
多種多樣的設計模式其實做的就是 封裝變化 ,面對不同的情景,分析什麼是變化的,什麼是不變的,封裝變化,使上層程式碼能夠“以不變應萬變”。

簡單工廠

【嚴格來說其並不算是一種設計模式,其就是把一堆判斷建立的語句放在了一個函式中,通過傳入的引數決定建立哪一個產品的例項】

在簡單工廠模式中,可以根據引數的不同返回不同類的例項。簡單工廠模式專門定義一個類來負責建立其他類的例項,被建立的例項通常都具有共同的父類。(這也是其優點:把實現物件的建立和物件的使用分離)

在實際開發中,還可以在呼叫時將所傳入的引數儲存在XML等格式的配置檔案中,修改引數時無須修改任何原始碼。

工廠類將全部建立邏輯集中到了一個工廠類中;它所能建立的類只能是事先考慮到的,如果需要新增新的類,則就需要改變工廠類了。

class Factory
{
public:
    static Product* CreateProduct(int n )
    {
        switch(n)
        {
        case 1:
            return new FirstProduct;
            break;
        case
2: return new SecondProduct; break; } } };

【適用環境】

  • 工廠類負責建立的物件比較少:由於建立的物件較少,不會造成工廠方法中的業務邏輯太過複雜。
  • 客戶端只知道傳入工廠類的引數,對於如何建立物件不關心:客戶端既不需要關心建立細節,甚至連類名都不需要記住,只需要知道型別所對應的引數。

由於簡單工廠很容易違反高內聚責任分配原則,因此一般只在很簡單的情況下應用。

工廠方法

【產品主體延遲到子類決定,產品的操作由基類實現,子類只負責具體建立某產品】

(變化的是產品,不變的是對產品的操作)

【動機】

由於簡單工廠模式的侷限性,比如:工廠現在能生產ProductA、ProductB和ProductC三種產品了,此時,需要增加生產ProductD產品;那麼,首先是不是需要在產品列舉型別中新增新的產品型別標識,然後,修改Factory類中的switch結構程式碼。這種對程式碼的修改量較大,易產生編碼上的錯誤。

工廠方法模式是簡單工廠模式的進一步抽象和推廣。由於使用了面向物件的多型性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。
在工廠方法模式中,核心的工廠類不再負責所有產品的建立,而是將具體建立工作交給子類去做。這個核心類僅僅負責給出具體工廠必須實現的介面,而不負責哪一個產品類被例項化這種細節,這使得工廠方法模式可以允許系統在不修改工廠角色的情況下引進新產品。

相似的產品有相同的操作,我們對產品的介面操作,讓產品的生成延遲到子類。(這些操作是固定的,但操作的目標是變化的)

不同的子類生產不同的產品,你選擇了哪種子類,就相當於你選擇了哪種產品。

這裡寫圖片描述

簡單示例程式碼:

class Factory
{
public:
    virtual Product *CreateProduct() = 0;
    void OperateProduct() ;  //對產品的操作框架
private:
    Product * productPtr ;
};

class FactoryA : public Factory
{
public:
    Product *CreateProduct()
    {
        return new ProductA ();
    }
};

class FactoryB : public Factory
{
public:
    Product *CreateProduct()
    {
        return new ProductB ();
    }
};

//對產品的操作,這些產品有共同的介面
void Factory::OperateProduct()
{
    //我們不知道具體是哪種產品被建立
    Product * productPtr = CreateProduct() ; 
    //但是我們知道怎麼使用這個產品
    productPtr->show();                      
}
int main(void)
{
    //當我們選擇了不同的工廠,我們實際就選擇了相應的產品
    Factory *factory = new FactoryB ();
    factory->OperateProduct();

    //........
}

【思考】

1、這和我們直接new一個產品物件有什麼區別?

    Product* product = new ProductB() ;
    product->show() ;
    product->change() ;
    區別是我們的工廠類中有圍繞著產品進行的對產品的操作。我們使用這個操作框架(我們保證這個操作框架是基本不變的)。我們直接對產品進行操作而不通過工廠類,則相同的對產品的操作程式碼會散落在各處,程式碼重用性不好。
    我們就是在一個框架中使用工廠方法的,框架使用抽象類定義物件之間的關係,這些物件的建立通常也由框架負責。(我們就是為了程式碼重用才使用框架的,不會說是直接使用產品類,在客戶程式碼中再重寫這些與產品相關的框架程式碼)

    【類似模板方法】
    工廠方法通常在模板方法中被呼叫。

2、使用模板以避免建立子類

    從上面的例子看到,在使用工廠方法時,我們要new一個相應的工廠類,然後在使用完畢後需要delete它。這樣做比較繁瑣。
    解決方式:我們可以把new的工廠類放入auto_ptr智慧指標類,由智慧指標負責管理我們動態分配的物件。
    或者,把工廠類設計成模板類

即:

template <class Product>
Product* StdFactory : public Factory
{
public:
    virtual Product* CreateProduct()
    {
        return new Product ;
    }
}

使用這個模板,我們就不需要再new工廠的子類了
StdFactory<ProductB> FactoryB ;

【缺點】

  • 在新增新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度,有更多的類需要編譯和執行,會給系統帶來一些額外的開銷。

【使用場景】

  • 1、當一個類不知道它所必須建立的物件的類的時候。(我現在需要使用這個類物件,但我又不知道這個類物件如何例項化)
  • 2、當一個類希望由它的子類來指定它所建立的物件的時候。

相關知識:
C++的智慧指標(我們可以在工廠類中新增一個智慧指標來儲存new的產品物件)

抽象工廠

【把一系列相關的元件,放在一個工廠建立,這樣只需換一個工廠,就可以換一個產品系列】

(變化的是產品的形態種類,不變的是對產品的操作集)

抽象工廠是諸多設計模式中最為“巨集偉”的一個模式。(這裡說的巨集偉,意為更換一個抽象工廠,對系統的面貌影響較大)

【動機】

  • 在工廠方法模式中具體工廠負責生產具體的產品,每一個具體工廠對應一種具體產品,但是有時候我們需要一個工廠可以提供多個產品物件,而不是單一的產品物件。

可以用“一橫一豎”來概括抽象工廠。

  • 一橫(產品的繼承結構):橫向擴充套件,平級的產品,如一個抽象類是電視機,其子類有海爾電視機、海信電視機、TCL電視機。抽象電視機是父類,而具體品牌的電視機是其子類。
  • 一豎(產品族):指由同一個工廠生產的,位於不同產品結構中的一組產品,如海爾電器工廠生產的海爾電視機、海爾電冰箱,海爾電視機位於電視機產品結構中,海爾電冰箱位於電冰箱產品結構中。

當你要強調一系列相關的產品物件的設計以便進行聯合使用時。

這裡寫圖片描述

【缺點】

  • 縱向擴充套件比較困難比如新增一個新的產品種類(比如:手機產品),這將涉及到對抽象工廠角色及其所有子類的修改,顯然會帶來較大的不便。(需要對海爾廠、海信廠、TCL廠都進行修改,新增它們的手機產品)

【使用場景】

  • 一個系統要由多個產品系列中的一個來配置時。
  • 當你要強調一系列相關產品物件的設計以便進行聯合使用時。

在很多軟體系統中需要更換介面主題,要求介面中的按鈕、文字框、背景色等一起發生改變時,可以使用抽象工廠模式進行設計。

一個抽象工廠建立了一個完整的產品系列。

抽象工廠只負責建立這些種類的產品,而不負責組裝這些產品為成品。

建造者模式

【將一個物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示】

(變化的是零件,不變的是對這些零件的組裝過程)

【動機】

  • 無論是在現實世界中還是在軟體系統中,都存在一些複雜的物件,它們擁有多個組成部分,如汽車,它包括車輪、方向盤、傳送機等各種部件。而對於大多數使用者而言,無須知道這些部件的裝配細節,也幾乎不會使用單獨某個部件,而是使用一輛完整的汽車,可以通過建造者模式對其進行設計與描述,建造者模式可以將部件和其組裝過程分開,一步一步建立一個複雜的物件。使用者只需要指定複雜物件的型別就可以得到該物件,而無須知道其內部的具體構造細節。

    複雜物件相當於一輛有待建造的汽車,而物件的屬性相當於汽車的部件,建造產品的過程就相當於組合部件的過程。由於組合部件的過程很複雜,因此,這些部件的組合過程往往被“外部化”到一個稱作建造者的物件裡,建造者返還給客戶端的是一個已經建造完畢的完整產品物件,而使用者無須關心該物件所包含的屬性以及它們的組裝方式,這就是建造者模式的模式動機。

這樣的話,我們可以方便地改變產品的內部表示,即可以方便地更換零件,產品的總結構不變。

這裡寫圖片描述

示例程式碼

int main(void)
{
    ConcreteBuilder * builder = new ConcreteBuilder();
    Director  director;
    director.setBuilder(builder);
    Product * pd =  director.constuct();
    pd->show();

    //......
}

Product* Director::constuct()
{
    m_pbuilder->buildPartA();
    m_pbuilder->buildPartB();
    m_pbuilder->buildPartC();

    return m_pbuilder->getResult();
}

【優點】

  • 在建造者模式中, 客戶端不必知道產品內部組成的細節,將產品本身與產品的建立過程解耦,使得相同的建立過程可以建立不同的產品物件。
  • 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者, 使用者使用不同的具體建造者即可得到不同的產品物件 。(有點像抽象工廠)

【缺點】

  • 建造者模式所建立的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,則不適合使用建造者模式。

【適用情景】

  • 需要生成的產品物件有複雜的內部結構,且這些產品物件的屬性相互依賴,需要指定其生成順序。
  • 隔離複雜物件的建立和使用,並使得相同的建立過程可以建立不同的產品。

【就是,當需要建立由許多零件裝配的物件時,使用建造者模式】

在很多遊戲軟體中,地圖包括天空、地面、背景等組成部分,人物角色包括人體、服裝、裝備等組成部分,可以使用建造者模式對其進行設計,通過不同的具體建造者建立不同型別的地圖或人物。

【總結】

    Builder類與抽象工廠類十分相似,但抽象工廠建立的是一系列有關的產品,建造者模式建立的是一系列有關的零件,最後還需要導演類來組裝零件成產品。(多了一個導演類來組裝零件,兩者的關注點不同)
    注意:導演只是對組裝過程進行引導,最終還是builder組裝併產出產品。(導演類中是包含建造者的,所有建立工作由建造者負責完成)導演呼叫builder一步一步把零件填裝到其空骨架中,最終返回一個成品。
    如果將抽象工廠模式看成 汽車配件生產工廠 ,生產一個產品族的產品,那麼建造者模式就是一個 汽車組裝工廠 ,通過對部件的組裝可以返回一輛完整的汽車。

    將工廠模式稍加變化可以得到建造者(Builder)模式。工場模式的“加工工藝”是隱藏的,而建造模式的“加工工藝”是暴露的。把工廠方法的一個方法分成做個方法步驟去構造一個產品,即為建造者模式。

單件模式

【用static函式返回此單件的指標】

懶漢式

懶漢式的特點是延遲載入,比如配置檔案的單例,採用懶漢式的方法,顧名思義,懶漢麼,很懶的,配置檔案的例項直到用到的時候才會載入。

class Singleton
{
public:
    static Singleton* Instance() ;
protected:
    Singleton() ;
private:
    static Singleton* _instance ;
} ;

//實現
Singleton* Singleton::_instance = 0 ;
Singleton* Singleton::Instance()
{
    if (_instance == 0)
    {
        _instance = new Singleton ;
    }

    return _instance ;
}

這種簡單的懶漢式單例,new出來的東西始終沒有釋放,雖然只有一份,不太可能造成記憶體洩露。
但我們可以在單例類中新增一個靜態物件專門用來釋放new出來的單例。

改進版懶漢:

class Singleton  
{  
public:  
    static Singleton * GetInstance()  
    {  
        if(_instance == NULL)  
            _instance = new CSingleton();  
        return _instance;  
    }  
protected:  
    CSingleton() { }  
private:
    static Singleton *_instance; 

    class CFreeInstance   
    {  
    public:  
        ~CFreeInstance()  
        {  
            if(Singleton::_instance)  
                delete CSingleton::_instance;  
        }  
    };  
    static CFreeInstance aFree;   

};  

餓漢式:

懶漢式單例有執行緒安全問題,在if判斷_instance是否為NULL時,多執行緒訪問會出現安全問題。

餓漢式的特點是一開始就載入了,餓漢式是執行緒安全的,在類建立的同時就已經建立好一個靜態的物件供系統使用,以後不再改變。

class Singleton  
{  
private:  
    Singleton() { }  
public:  
    static Singleton * GetInstance()  
    {  
        static Singleton _instance;   
        return &_instance;  
    }  
}; 

【參考】
《設計模式之禪》
《GoF設計模式》