如果你在你的某個系統中增加了乙個子類,你要建立這個子類的物件,但又不想改變任何原有**,有可能麼?
答案是肯定的,用「物件工廠」設計模式。
物件工廠(object factory)是gof 23種設計模式之外的模式,它既不是抽象工廠(abstract factory),也不是工廠方法(factory method),儘管可能跟它們有些淵源。我第一次看到介紹「物件工廠」的書是《c++設計新思維(modern c++ design)》,但我第一次看到物件工廠的**,卻比看到這書早,但我當時不知道它叫「物件工廠」。
)第8章詳細講解了我們為什麼會需要物件工廠,如何實現並泛化它等內容。本文並不想重複這些內容,而是想通過乙個小例子,將使用物件工廠和不使用物件工廠的情況進行對比,來說明物件工廠會帶來哪些好處。
還是物件導向教科書上那個經典的shape的例子。基於多型,你用c++編寫了一套關於形狀的系統,shape是基類。可能你已經有了line、rectangle等子類。在你的客戶程式裡,通過傳入形狀的型別標識(假設我們用字串來標識型別,當然用整型來標識也可以)來建立具體的(concrete)shape。你的**看起來可能是這樣。
shape
*createshapebyid(
const
std::
string
&strshapeid)
else
if(strshapeid =="
rectangle")
return
pshape;
}這像是gof《設計模式》裡所說的引數化工廠方法。但這裡違反了物件導向的最重要的規則:
1.它基於型別標記執行了if-else語句(當用整型標識而換為switch語句時同理),這正是物件導向程式竭力消除的東西。
2.它在乙個原始碼檔案中收集所有關於shape子類的相關資訊,這也是我們應該竭力避免的。客戶**檔案都因此必須包含其標頭檔案,造成編譯依存性和維護上的瓶頸。
3.它難以擴充。現在你需要增加ellipse子類,如果沒有使用物件工廠模式,除了增加ellipse本身的**,你至少還要增加以下**:
a)在你的客戶**檔案裡,增加
#include
"ellipse.h"b)
在createshapebyid中加入以下**:
else
if(strshapeid =="
ellipse")
c)如果你用整型定義型別標識,你還要定義
ellipse
形狀的型別標識。比如:
#define
ellipse 3
現在讓我們來改改,用物件工廠來實現。泛化的物件工廠的**如下:
template
<
class
abstractproduct,
class
identifiertype,
typename productcreator
=abstractproduct*(
*)()
>
class
factory
;factory(factory
&factory);
factory
&operator=(
const
factory
&factory);
public
:bool
register(
const
identifiertype
&id , productcreator creator)
bool
unregister(
const
identifiertype
&id)
abstractproduct
*createobject(
const
identifiertype
&id)
return
null;
}static
factory
*instance()
return
pfactory;
}private
:typedef std::map
<
identifiertype, productcreator
>
assocmap;
assocmap associations_;
};簡單說說其工作機理。更詳細的、深入的內容還是請看《c++設計新思維》。
1.此物件工廠泛化了3樣東西:
a)抽象產品(abstract product)。對應本例,就是shape。
b)產品型別識別符號(product type identifier)。對應本例,我們用字串標識,就是std::string。
c)產品生產者(product creator)。對應本例,我們將用預設的(也是最簡單的)原型,也就是無引數、返回值為抽象產品指標的函式。
2.它使用std::map作為產品型別識別符號與產品生產者的對映的儲存結構。
3.register負責向map中註冊乙個產品型別識別符號與產品生產者的對映,unregister則負責登出。
4.createobject是物件工廠的核心,它會根據傳入的產品型別識別符號,找到對應的產品生產者,並呼叫它,建立出具體產品(concrete product)。
5.instance是實現了物件工廠的單件模式。這裡用的是「meyers singleton」的乙個變種。當然這裡不是討論singleton的地方。
有了物件工廠,我們再在shape.h裡定義乙個用來註冊shape具體類的模板類,這裡有真正的形狀的生產者(create函式)。**如下:
template
<
class
derivedshape
>
class
registershapeclass
registershapeclass(
const
std::
string
&strshapeid)
};再定義乙個將類名轉換為字串的巨集:
#define
classnametostring(x) #x
好了,有了物件工廠,createshapebyid就變成這樣:
shape
*createshapebyid(
const
std::
string
&strshapeid)
首先,這個函式短多了,而且不會隨著子類的增加而膨脹,但這不是關鍵。這裡面沒有對具體shape型別的引用。當我們需要增加ellipse子類,只需在ellipse類自己的**裡加上下面這句(向工廠註冊自己),而不需要改變任何原有**!
registershapeclass
<
ellipse
>
registerellipse(classnametostring(ellipse));
這看起來有些奇異,但更奇異的是,不僅從原有**中我們看不到任何引用新子類的**,而且連linker都會認為新子類沒有被引用,而將新子類的obj排除在link之外。當然,你也許會認為link的時候使用/opt:noref選項可以避免這個問題。但現實是, visual c++(從vc6到vc9)的/opt:noref選項都有乙個問題(參見
):即使用此選項,仍然不能將新子類的obj檔案link進去。解決的辦法也是在這個**裡看到的,加入類似下面這樣一句,以使linker強行將registershapeclass連線進去
:#pragma
comment(linker, "/include:??0?$registershapeclass@vellipe@@@@qae@abv?$basic_string@du?$char_traits@d@std@@v?$allocator@d@2@@std@@@z")
在我曾開發過的醫學影象處理系統中,需要用到物件工廠的地方,至少有3種:
1.由使用者輸入型別,系統動態生成對應的物件實體。比如:使用者選擇不同的測量工具(measurement,包括:distance,angle等),還有下達各種影象操作的命令(command,比如:zoom,pan,rotate等)。
2.序列化。比如上條所說的measurement,我們能夠儲存下來,並能夠在某個時刻恢復(restore)。儲存時,用measurement的名稱來標識測量工具的型別並序列化,恢復時,根據這個型別標識動態生成物件,並反序列化。
3.同步。我們稱其為會議模式(conference mode),比如乙個客戶端上畫出的measurement,其它客戶端上能同步看到,我們使用xml並進行流(std::stringstream)的輸入和輸出,來傳輸和同步資料和狀態。當資料在其它客戶端流入的時候,與反序列化相似,根據型別標識動態生成物件。
物件工廠使得客戶不再需要(或較少)改變原有系統,但卻很容易擴充套件系統。所以說:物件工廠是對oo開閉原則(ocp,對變更關閉,對擴充套件開放)非常好的闡釋。
這裡再多說一句:由於.net的反射(reflection)機制,使我們不用自己再去建造物件工廠就可以動態地生成物件。用c#寫出來的**應該類似於這樣:
string
strshapeid ="
ellipse";
type type
=type.gettype(strshapeid);
shape shape
=(shape)activator.createinstance(type,
newobject );
物件導向設計模式(工廠模式)
1 單一職責原則 其實就是 高內聚,低耦合 每個類應該只有乙個職責,對外只能提供一種功能,而引起類變化的原因應該只有乙個。2 開閉原則 核心思想就是 對擴充套件開放,對修改關閉 3 裡式替換原則 核心思想 在任何父類出現的地方都可以用子類來替代。也就是說在同乙個繼承體系中的物件應該有共同的行為特徵。...
物件建立型設計模式 抽象工廠模式
本文章根據劉偉 sunny 的設計模式一書記錄的筆記,感謝作者的知識分享。抽象工廠模式 abstract factory pattern 提供乙個建立一系列相關或相互依賴物件的介面,而無須指定它們具體的類。抽象工廠模式又稱為kit模式,它是一種物件建立型模式。在本例項中我們對 進行了大量簡化,實際使...
物件導向設計原則 設計模式之簡單工廠 工廠方法
在實際的開發中,我們要想更深入的了解物件導向思想,就必須熟悉前人總結過的物件導向的思想的設計原則 1.單一職責原則 2.開閉原則 3.黎克特制替換原則 4.依賴注入原則 5.介面分離原則 6.迪公尺特原則 概述 設計模式 design pattern 是一套被反覆使用 多數人知曉的 經過分類編目的 ...