“對象建立”模式
通過“對象建立” 模式繞開new,來避免對象建立(new)過程中所導緻的緊耦合(依賴具體類),進而支援對象建立的穩定。它是接口抽象之後的第一步工作。
典型模式
•Factory Method
•Abstract Factory
•Prototype
•Builder
1. Prototype 原型模式
動機(Motivation)
- 在軟體系統中,經常面臨着“某些結構複雜的對象"的建立工作;由于需求的變化,這些對象經常面臨着劇烈的變化,但是它們卻擁有比較穩定一緻的接口。
- 如何應對這種變化?如何向“客戶程式(使用這些對象的程式) "隔離出“這些易變對象”,進而使得“依賴這些易變對象的客戶程式"不随着需求改變而改變?
代碼示例
首先看一下工廠模式的代碼
//抽象類
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
//工廠基類
class SplitterFactory{
public:
virtual ISplitter* CreateSplitter()=0;
virtual ~SplitterFactory(){}
};
将兩個類合并,将CreateSplitter函數重命名為Clone函數,得到代碼如下
//抽象類
class ISplitter{
public:
virtual void split()=0;
virtual ISplitter* clone()=0; //通過克隆自己來建立對象
virtual ~ISplitter(){}
};
//具體類
class BinarySplitter : public ISplitter{
public:
virtual ISplitter* clone(){
return new BinarySplitter(*this); // 通過拷貝構造函數建立對象
}
};
class TxtSplitter: public ISplitter{
public:
virtual ISplitter* clone(){
return new TxtSplitter(*this);
}
};
class PictureSplitter: public ISplitter{
public:
virtual ISplitter* clone(){
return new PictureSplitter(*this);
}
};
class MainForm : public Form
{
ISplitter* prototype;//原型對象
public:
MainForm(ISplitter* prototype){
this->prototype=prototype;
}
void Button1_Click(){
ISplitter * splitter=
prototype->clone(); //克隆原型得到一個新對象
splitter->split();
}
};
模式定義
使用原型執行個體指定建立對象的種類,然後通過拷貝這些原型來建立新的對象。
什麼時候使用
對于結構複雜的對象,有一個對象已經達到比較好的狀态,将其作為原型,可以克隆出許多。
要點總結
- Prototype模式同樣用于隔離類對象的使用者和具體類型(易變類)之間的耦合關系,它同樣要求這些“易變類”擁有“穩定的接口"。
- Prototype模式對于“如何建立易變類的實體對象”采用"原型克隆"的方法來做,它使得我們可以非常靈活地動态建立“擁有某些穩定接口"的新對象——所需工作僅僅是注冊一個新類的對象(即原型)然後在任何需要的地方Clone.
- Prototype模式中的Clone方法可以利用某些架構中的序列化來實作深拷貝。
網友對原型的介紹 https://www.cnblogs.com/iplus/archive/2012/05/02/4490233.html
2. Builder 建構器
動機(Motivation)
在軟體系統中,有時候面臨着“一個複雜對象”的建立工作,其通常由各個部分的子對象用一定的算法構成;由于需求的變化,這個複雜對象的各個部分經常面臨着劇烈的變化,但是将它們組合在一起的算法卻相對穩定。
如何應對這種變化?如何提供一種“封裝機制”來隔離出“複雜對象的各個部分”的變化,進而保持系統中的“穩定建構算法”不随着需求改變而改變?
代碼示例
遊戲裡面建房子,可以建茅草屋、磚瓦房等等。但有幾個流程是固定的,例如打地基、建牆壁、建房頂等等。不同房子的牆壁、窗子等等可能是不一樣的。
class House{
public:
// 建房子的流程,穩定的(很像模闆方法)
void Init(){ // 初始化
this->BuildPart1();
for (int i = 0; i < 4; i++){
this->BuildPart2();
}
bool flag = this->BuildPart3();
if (flag){
this->BuildPart4();
}
this->BuildPart5();
}
virtual ~House(){}
protected:
// 建房子的具體步驟,可能是變化的,是以定為虛函數
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
class StoneHouse: public House{
//...
protected:
//重寫虛函數
virtual void BuildPart1(){
}
virtual void BuildPart2(){
}
virtual void BuildPart3(){
}
virtual void BuildPart4(){
}
virtual void BuildPart5(){
}
};
int main(){
House* pHouse = new StoneHouse();// 建構對象
pHouse->Init();
}
在C++中,如果在構造函數裡面調用虛函數,是靜态綁定,是以不能将Init函數中的内容變為構造函數。
在StoneHouse等子類中,對虛函數進行重寫,當時構造過程Init是不變的。到這裡已經基本完成了Build模式。但仍有優化空間:
将對象的表示和建構進行拆分。
// 将House和Housebuilder分離
class House{ //對象的表示
//....
};
class HouseBuilder { // 對象的建構
public:
House* GetResult(){
return pHouse;
}
virtual ~HouseBuilder(){}
protected:
House* pHouse;
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
class StoneHouse: public House{
};
class StoneHouseBuilder: public HouseBuilder{
protected:
virtual void BuildPart1(){
//pHouse->Part1 = ...;
}
virtual void BuildPart2(){
}
virtual void BuildPart3(){
}
virtual void BuildPart4(){
}
virtual void BuildPart5(){
}
};
class HouseDirector{
public:
HouseBuilder* pHouseBuilder;
HouseDirector(HouseBuilder* pHouseBuilder){ // 構造函數,傳入具體類型
this->pHouseBuilder=pHouseBuilder;
}
House* Construct() { // 穩定的
pHouseBuilder->BuildPart1();
for (int i = 0; i < 4; i++){
pHouseBuilder->BuildPart2();
}
bool flag = pHouseBuilder->BuildPart3();
if (flag){
pHouseBuilder->BuildPart4();
}
pHouseBuilder->BuildPart5();
return pHouseBuilder->GetResult();
}
};
模式定義
将一個複雜對象的建構與其表示相分離,使得同樣的建構過程(穩定)可以建立不同的表示(變化)。
Director相當于HouseDirector,是穩定的,Builder相當于HouseBuilder,也是穩定的,ConcreteBuilder相當于StoneHouseBuilder,是變化的。如果類不是很複雜,Builder和Director可以合在一起,不用拆分。
要點總結
Builder 模式主要用于“分步驟建構一個複雜的對象”。在這其中“分步驟”是一個穩定的算法,而複雜對象的各個部分則經常變化。
變化點在哪裡,封裝哪裡—— Builder模式主要在于應對“複雜對象各個部分”的頻繁需求變動。其缺點在于難以應對“分步驟建構算法”的需求變動。
在Builder模式中,要注意不同語言中構造器内調用虛函數的差别(C++ vs. C#) 。