大衛的Design Patterns學習筆記02:Factory
一、概述Factory(工廠)模式用於封裝對象的建立過程,將對象的建立獨立成單獨的程式模組,從而提高整個應用系統的Flexibility。
二、結構主要有以下三種Factory模式:1、Simple Factory模式:專門定義一個類來負責建立其它類的執行個體,被建立的執行個體通常都具有共同的父類。圖1:Simple Factory的類圖2、Factory Method模式:將對象的建立交由父類中定義的一個標準方法來完成,而不是其建構函式,究竟應該建立何種對象由具體的子類負責決定。圖2:Factory Method的類圖3、Abstract Factory模式:提供一個共同的介面來建立相互關聯的多個對象。圖3:Abstract Factory的類圖 三種Factory模式的區別在於:Simple Factory在於對產品建立過程的簡單封裝,它簡單地根據輸入來決定建立何種產品(這些產品不一定屬於同一產品族),因此,任何產品種類的更新都將對Simple Factory的代碼造成影響;Factory Method面對的是一個產品族,它引入了ConcreteFactory來決定建立產品族中的何種產品,當產品種類增加時,只需建立新的ConcreteFactory來建立新的產品;而Abstract Factory面對的則是多個產品系列,它是Factory Method的延伸,Abstract Factory在一個ConcreteFactory中包含了多個Factory Method,以用於建立多個不同產品族中的多個產品。需要注意的是,以上所說的產品並非僅限於單個的產品,可以包括一次建立出來的一組相同或者相關產品,從這個意義上講,三種Factory特別是Factory Method與Abstract Factory之間的界限並非十分明顯。
三、應用三種Factory模式均用於對象的建立,但Factory Method與Abstract Factory由於引進了一個用於定義介面的基類Factory,可以更好地屏蔽內部的建立過程,因此,可以做到客戶代碼與實現代碼的完全隔離(如樣本部分的COM類廠);而Simple Factory在這一點上表現則不佳,因此,往往仍然只是客戶代碼的一部分,只是將對象建立的工作納入自己管轄之下而已。但並不是說Abstract Factory就比Simple Factory有用,三種Factory模式的實質是一樣的,即用於封裝對象的建立過程,在實際使用中應根據自己的需要加以選擇。
四、優缺點優點:由於對對象建立過程的封裝,不但使得代碼的維護工作變得更加簡單(職責分離了,明確了,找問題變得更加容易),同時也使得應用系統對於新增產品變得不那麼敏感,當新增產品時,我們只需要修改Factory代碼或者擴充新的子類來進行產品的建立,靈活性和可重用性都得到了提高。缺點:由於職責的分離以及類的數目的增多,代碼量的增加往往是不可避免的,同時,隨著產品種類的變更,類層次將不可避免發生幾何膨脹。
五、樣本1、Simple Factory樣本<Java Design Patterns A Tutorial>一書中還給出了一個很巧妙的Simple Factory的應用執行個體。該執行個體如下:一般的英文人名有兩種寫法,"firstname lastname"或者"lastname, firstname",現在我們有一個通訊錄程式需要根據輸入的人名資訊(可能是上面的任意一種形式)分別解析出輸入的firstname和lastname,儲存到通訊錄檔案中。如何解決這個問題呢?由於問題本身比較簡單,我們可以簡單地藉助if等來解決這一問題。但是,如果我們的需求突然發生變化,我們還要支援一些其它的形式,如firstname abbreviation.,甚至更多的情況,這時我們怎麼辦呢?如果我們最初採用的是if等判斷語句,顯然我們需要修改我們解析資料部分的代碼,增加更多的分支,所有的代碼可能需要重新進行嚴格的測試。那麼,有沒有比較OO,比較靈活的解決方案呢?<Java Design Patterns A Tutorial>給了我們如下的解決方案(Java Code): public class Namer { //base class extended by two child classes protected String last; //split name protected String first; //stored here public String getFirst() { return first; //return first name } public String getLast() { return last; //return last name }} public class FirstFirst extends Namer { //extracts first name from last name //when separated by a space public FirstFirst(String s) { int i = s.lastIndexOf(" "); //find sep space if (i > 0) { first = s.substring(0, i).trim(); last = s.substring(i + 1).trim(); } else { first = ""; // if no space last = s; // put all in last name } }} public class LastFirst extends Namer { // extracts last name from first name // when separated by a comma public LastFirst(String s) { int i = s.indexOf(","); //find comma if (i > 0) { last = s.substring(0, i).trim(); first = s.substring(i + 1).trim(); } else { last = s; //if no comma, first = ""; //put all in last name } }} public class NamerFactory { //Factory decides which class to return based on //presence of a comma public Namer getNamer(String entry) { //comma determines name order int i = entry.indexOf(","); if (i > 0) return new LastFirst(entry); else return new FirstFirst(entry); }} 整個實現方案的類圖如下:圖4. Namer, NamerFactory及類圖圖中採用的是與圖1不同的一種結構,是圖1所示的基本的SimpleFactory變形後的結果。當新增需求時,我們要做的是從Namer派生新的輸入格式封裝類,並對NamerFactory中的代碼進行簡單修改,根據輸入建立新的封裝類執行個體。 2、Factory Method樣本應用Factory Method的最典型的例子當數COM的類廠,以下是一段典型的COM建立並運用組件提供的介面方法的代碼:IUnknown *pUnk=NULL;IObject *pObject=NULL;CoInitialize(NULL);CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void**)&pUnk);pUnk->QueryInterface(IID_IOjbect, (void**)&pObject);pUnk->Release();pObject->Func();pObject->Release();CoUninitialize();當我們執行上述操作時,類廠在背後默默地為我們完成了建立組件類對象的工作。下面是CoCreateInstance內部實現的虛擬碼:CoCreateInstance(....){ ....... IClassFactory *pClassFactory=NULL; CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pClassFactory); pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk); pClassFactory->Release(); ........}這段話的意思就是先得到類廠對象,再通過類廠建立組件從而得到IUnknown指標。繼續深入一步,看看CoGetClassObject的內部偽碼:CoGetClassObject(.....){ //通過查註冊表CLSID_Object,得知組件DLL的位置、檔案名稱 //裝入DLL庫 //使用函數GetProcAddress(...)得到DLL庫中函數DllGetClassObject的函數指標。 //調用DllGetClassObject}DllGetClassObject是幹什麼的,它是用來獲得類廠對象的,只有先得到類廠才能去建立組件。下面是DllGetClassObject的偽碼:DllGetClassObject(...){ ...... CFactory* pFactory= new CFactory; //類廠對象 //查詢IClassFactory指標 pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory); pFactory->Release(); ......}CoGetClassObject的流程已經到此為止,現在返回CoCreateInstance,看看CreateInstance的偽碼:CFactory::CreateInstance(.....){ ........... CObject *pObject = new CObject; //組件對象 pObject->QueryInterface(IID_IUnknown, (void**)&pUnk); pObject->Release(); ...........}以下是上述建立過程的圖示:圖5. CoCreateInstance流程圖關於組件建立流程的更為詳細的論述,請參見<COM技術內幕>。如果沒有類廠的協助,而是將組件類的建立工作交由我們自己完成,則我們必須知道組件類的類名,而如果這樣,則COM的封裝性和可重用性都將大大受損。正是由於運用了Factory Method,COM組件只負責對外提供介面,封裝內部實現,包括建立過程的特性才得以完整體現。 在MFC中,CDocTemplate是另一個典型的應用Factory Method的例子,MFC實現中通過派生不同的子類CSingleDocTemplate/CMultiDocTemplate來支援不同的文件類型,感興趣的朋友可以自己研究CDocTemplate及其子類的相關代碼,相關代碼在afxwin.h中,與上面討論的Factory Method不同的是,這裡的文件類型(單文檔/多文檔)並沒有嚴格一致的類與之對應。 以下是一個簡單的Factory Method的例子(Java Code): // Abstract Product Classabstract class Button { public String caption; public abstract void paint();} // ConcreteProduct1class WinButton extends Button { public void paint() { System.out.println("I'm a WinButton: " + caption); }} // ConcreteProduct2class MOTButton extends Button { public void paint() { System.out.println("I'm a MOTButton: " + caption); }} // Abstract Factory Classabstract class GUIFactory { // Simple Factory Method public static GUIFactory getFactory(String type) { if (type.equals("WIN")) { return(new WinFactory()); } else if (type.equals("MOT")) { return(new MOTFactory()); } else return null; } // factory method public abstract Button createButton();} // ConcreteFactory1class WinFactory extends GUIFactory { public Button createButton() { return(new WinButton()); }} // ConcreteFactory2class MOTFactory extends GUIFactory { public Button createButton() { return(new MOTButton()); }} class FactoryMethodTest { public static void main(String[] args) { if (args.length != 1 || (!args[0].equals("WIN") && !args[0].equals("MOT"))) { System.out.println("Usage: java FactoryMethodTest [WIN|MOT]"); return; } GUIFactory aFactory = GUIFactory.getFactory(args[0]); Button aButton = aFactory.createButton(); aButton.caption = "Play"; aButton.paint(); } //output is //I'm a WinButton: Play //or //I'm a MOTButton: Play} 其中,我們通過從抽象基類GUIFactory派生子類WinFactory/MOTFactory分別建立不同的Button類系中的不同產品WinButton/MOTButton。 3、Abstract Factory樣本前面已經說過,Abstract Factory只是Factory Method的一種擴充,如果將上面例子中抽象工廠類GUIFactory的介面改成:// Abstract Factory Classabstract class GUIFactory { // Simple Factory Method public static GUIFactory getFactory(String type) { ... } public abstract Button createButton(); public abstract Button createPanel(); // more factory methods}以對應多個不同的產品類系,並在各子類中實現新增各factory methods,則上面的樣本就變成一個Abstract Factory了。 (附註:也許看完了本系列的下一篇:Builder模式,再回過頭來看這個例子,你會發現如果再增加一個用於組裝的Director類,則上面的例子還可以成為Builder模式的一個應用執行個體。)