將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
當我們在構造一個窗口控件的時候,往往包含三個方面的初始化工作:
UI初始化動畫初始化信號槽初始化這樣我們就可以構造好一個窗口控件了,我們可以看以下類圖:
乍一看該實現並沒有什麼問題CenterWidget
類在其構造函數中調用了initUi
、initAnimation
、initSlot
三個私有成員函數分別進行Ui,動畫,信號槽的初始化。可是當我們進行一個新的CenterWidget
構造時,要求動畫效果改變,這時候需要怎麼做呢?修改initAnimation
成員函數?作為一名優秀的程序員,提到修改類的時候就應該警惕,這個設計明顯違反了開閉原則。這時候我們遇到到一個問題:構造的接口是固定的,構造的順序是固定的,而要求構造的內容變化。
我們直覺上,這個問題的解就是:initUi
,initAnimation
,initSlot
三個成員函數必須為虛函數。在需要動畫、Ui或者信號槽發生變化的時候,只要需要添加新的子類,重寫其中某個函數需要變化即可。可是,當這三個成員函數都成為虛函數之後,就不可能在構造函數中調用,因為
在一個類構造期間,vtable還沒有初始化完成,虛函數機制不會正確工作。
於是我們增加了一個Init
方法,在對象被構造出來之後調用之。此時類圖如下:
對於這個類圖來說,解決了開閉原則的問題,可是新的問題出現了,客戶端需要調用Init
,有違反了接口隔離的和單一職責原則之嫌疑,為何這麼說呢?
客戶端的想法是:
CenterWidget
實例然後將CenterWidget
顯示出來
而CenterWidget
提供的卻是:
CenterWidget
實例然後調用Init
接口初始化然後將CenterWidget
顯示出來
對於客戶端來說,它需要依賴一個它不需要的接口Init
,這個Init
不是客戶端所要求的,反而更象是客戶端需要CenterWidget
作為主窗口的邏輯功能而強行搭配的一個接口(注:接口隔離原則指的是客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。)。其次,作為主窗口類的CenterWidget
需要額外負責管理復雜的構造邏輯。這倒不是什麼嚴重的問題,很多時候這種解決方法都是行之有效的,不過我們提出一種更為優雅的解決方法。
為了解決上節所述的問題,我們將整個CenterWidget
構造職責抽取出來由單獨的一個Builder
類承擔。這樣將initUi
、initAnimation
、initSlot
函數轉移到Builder
類中。由Builder
類構造出來CenterWidget
實例,另外,有時候對initUi
、initAnimation
、initSlot
三個函數調用次序有所要求,引入一個Director
類專門管理對initUi
、initAnimation
、initSlot
的調用次序,指揮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
建造者模式的主要優點 :
將產品本身和產品的創建過程解耦,使得不同的創建過程創建出不同的實例可以很方便地增加新的建造者,實現新的產品實例的創建,符合開閉原則建造者模式的主要缺點 :
建造者模式只能創建具有許多共同點的產品,組成成分相似如果產品內部組成復雜多變,將需要定義大量的建造者類,使得系統復雜化使用場景 :
需要創建的產品具有多個組成部分且內部構造復雜需要指定產品的創建順序對象創建的過程需要獨立於該類