程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++設計模式之建造者模式解決

C++設計模式之建造者模式解決

編輯:關於C++

C++設計模式之建造者模式

將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。

 

 

一、緣由

當我們在構造一個窗口控件的時候,往往包含三個方面的初始化工作:

UI初始化動畫初始化信號槽初始化

這樣我們就可以構造好一個窗口控件了,我們可以看以下類圖:

乍一看該實現並沒有什麼問題CenterWidget類在其構造函數中調用了initUiinitAnimationinitSlot三個私有成員函數分別進行Ui,動畫,信號槽的初始化。可是當我們進行一個新的CenterWidget構造時,要求動畫效果改變,這時候需要怎麼做呢?修改initAnimation成員函數?作為一名優秀的程序員,提到修改類的時候就應該警惕,這個設計明顯違反了開閉原則。這時候我們遇到到一個問題:構造的接口是固定的,構造的順序是固定的,而要求構造的內容變化。
我們直覺上,這個問題的解就是:initUi,initAnimation,initSlot三個成員函數必須為虛函數。在需要動畫、Ui或者信號槽發生變化的時候,只要需要添加新的子類,重寫其中某個函數需要變化即可。可是,當這三個成員函數都成為虛函數之後,就不可能在構造函數中調用,因為
在一個類構造期間,vtable還沒有初始化完成,虛函數機制不會正確工作。
於是我們增加了一個Init方法,在對象被構造出來之後調用之。此時類圖如下:

對於這個類圖來說,解決了開閉原則的問題,可是新的問題出現了,客戶端需要調用Init,有違反了接口隔離的和單一職責原則之嫌疑,為何這麼說呢?
客戶端的想法是:

我想要一個CenterWidget實例然後將CenterWidget顯示出來

CenterWidget提供的卻是:

給你一個CenterWidget實例然後調用Init接口初始化然後將CenterWidget顯示出來

對於客戶端來說,它需要依賴一個它不需要的接口Init,這個Init不是客戶端所要求的,反而更象是客戶端需要CenterWidget作為主窗口的邏輯功能而強行搭配的一個接口(注:接口隔離原則指的是客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。)。其次,作為主窗口類的CenterWidget需要額外負責管理復雜的構造邏輯。這倒不是什麼嚴重的問題,很多時候這種解決方法都是行之有效的,不過我們提出一種更為優雅的解決方法。

二、實現

為了解決上節所述的問題,我們將整個CenterWidget構造職責抽取出來由單獨的一個Builder類承擔。這樣將initUiinitAnimationinitSlot函數轉移到Builder類中。由Builder類構造出來CenterWidget實例,另外,有時候對initUiinitAnimationinitSlot三個函數調用次序有所要求,引入一個Director類專門管理對initUiinitAnimationinitSlot的調用次序,指揮Builder的工作,這就是建造者模式。建造者模式的示意圖和使用了建造者模式的CenterWidget建造方案如下圖所示:

建造者模式

使用建造者模式的`CenterWidget`方案

三、代碼分析

下面給出CenterWidget方案的示例代碼

#include 
#include 

using std::string;


class CenterWidget {
private:
    string Ui;
    string Animation;
    string Slot;
public:
    virtual ~CenterWidget (){};
    void setUi(const string& x){
        Ui = x;
    }

    void setAnimation(const string& x){
        Animation = x;
    }

    void setSlot(const string& x){
        Slot = x;
    }

    void show(){
        std::cout << "Ui = " << Ui  <<
        "  Animation = " << Animation  <<
        "  Slot = " << Slot << std::endl;
    }
};

class CenterWidgetBuilder {
public:
    virtual ~CenterWidgetBuilder(){}
    virtual void initUi() = 0;
    virtual void initAnimation() = 0;
    virtual void initSlot() = 0;
    virtual CenterWidget *getResult() = 0;
};

class ConcreteCenterWidgetBuilderA : public CenterWidgetBuilder {
private:
    CenterWidget *curWidget;
public:
    ConcreteCenterWidgetBuilderA():curWidget(new CenterWidget){}
    virtual ~ConcreteCenterWidgetBuilderA(){
        delete curWidget;
    };
    virtual void initUi(){
        curWidget->setUi("Q Ui");
    }
    virtual void initAnimation(){
        curWidget->setAnimation("Biu~Biu~Biu~");
    }
    virtual void initSlot(){
        curWidget->setSlot("connected to your heart");
    }

    CenterWidget *getResult(){
        return curWidget;
    };
};

class ConcreteCenterWidgetBuilderB : public CenterWidgetBuilder {
private:
    CenterWidget *curWidget;
public:
    ConcreteCenterWidgetBuilderB():curWidget(new CenterWidget){}
    virtual ~ConcreteCenterWidgetBuilderB(){
        delete curWidget;
    };
    virtual void initUi(){
        curWidget->setUi("Q Ui");
    }
    virtual void initAnimation(){
        curWidget->setAnimation("Boom~Boom~Boom~");
    }
    virtual void initSlot(){
        curWidget->setSlot("connected to your heart");
    }

    CenterWidget *getResult(){
        return curWidget;
    };
};

class Dirctor {
private:
    CenterWidgetBuilder *Builder;
public:
    Dirctor (CenterWidgetBuilder* builder):Builder(builder){};
    virtual ~Dirctor(){delete Builder;}
    void Construct(){
        Builder->initUi();
        Builder->initAnimation();
        Builder->initSlot();
    }
};

int main(void)
{
    ConcreteCenterWidgetBuilderA *builderA = new ConcreteCenterWidgetBuilderA;
    Dirctor *directorA = new Dirctor(builderA);
    directorA->Construct();
    builderA->getResult()->show();

    ConcreteCenterWidgetBuilderB *builderB = new ConcreteCenterWidgetBuilderB;
    Dirctor *directorB = new Dirctor(builderB);
    directorB->Construct();
    builderB->getResult()->show();
}
運行結果:
Ui = Q Ui  Animation = Biu~Biu~Biu~  Slot = connected to your heart
Ui = Q Ui  Animation = Boom~Boom~Boom~  Slot = connected to your heart

ConcreteCenterWidgetBuilderB和ConcreteCenterWidgetBuilderA中有重復代碼,為了方便代碼復用,我們可以使用ConcreteCenterWidgetBuilderB繼承ConcreteCenterWidgetBuilderA,然後重寫需要變化的initAnimation即可,代碼修改如下:


class ConcreteCenterWidgetBuilderA : public CenterWidgetBuilder {
protected:  //為了子類能訪問之,改為protected
    CenterWidget *curWidget;
public:
    ConcreteCenterWidgetBuilderA():curWidget(new CenterWidget){}
    virtual ~ConcreteCenterWidgetBuilderA(){
        delete curWidget;
    };
    virtual void initUi(){
        curWidget->setUi("Q Ui");
    }
    virtual void initAnimation(){
        curWidget->setAnimation("Biu~Biu~Biu~");
    }
    virtual void initSlot(){
        curWidget->setSlot("connected to your heart");
    }

    CenterWidget *getResult(){
        return curWidget;
    };
};

class ConcreteCenterWidgetBuilderB : public ConcreteCenterWidgetBuilderA {
public:
    ConcreteCenterWidgetBuilderB(){}
    virtual ~ConcreteCenterWidgetBuilderB(){ };
    //只重寫需要改變的部分
    virtual void initAnimation(){
        curWidget->setAnimation("Boom~Boom~Boom~");
    }
};

運行結果:
Ui = Q Ui  Animation = Biu~Biu~Biu~  Slot = connected to your heart
Ui = Q Ui  Animation = Boom~Boom~Boom~  Slot = connected to your heart

四、總結

建造者模式的主要優點 :

將產品本身和產品的創建過程解耦,使得不同的創建過程創建出不同的實例可以很方便地增加新的建造者,實現新的產品實例的創建,符合開閉原則

建造者模式的主要缺點 :

建造者模式只能創建具有許多共同點的產品,組成成分相似如果產品內部組成復雜多變,將需要定義大量的建造者類,使得系統復雜化

使用場景 :

需要創建的產品具有多個組成部分且內部構造復雜需要指定產品的創建順序對象創建的過程需要獨立於該類    
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved