一、模式概述
也許Factory Method模式是設(shè)計(jì)模式中應(yīng)用最廣泛的模式。在面向?qū)ο蟮脑O(shè)計(jì)中,關(guān)于對(duì)象的管理是其核心所在,而其中對(duì)象的創(chuàng)建則是對(duì)象管理的第一步。對(duì)象的創(chuàng)建非常簡(jiǎn)單,在C#中,只需要應(yīng)用new操作符調(diào)用對(duì)象的構(gòu)造函數(shù)即可,然而創(chuàng)建對(duì)象的時(shí)機(jī)卻非常重要。
首先我們從對(duì)象的特征來(lái)看,代表抽象關(guān)系的類型,如接口和抽象類,是不能創(chuàng)建的,換句話說(shuō),我們要?jiǎng)?chuàng)建的對(duì)象都是與具體的對(duì)象類型有關(guān)。因此,對(duì)象的創(chuàng)建工作必然涉及到設(shè)計(jì)中的實(shí)現(xiàn)細(xì)節(jié),從而導(dǎo)致創(chuàng)建者與具體的被創(chuàng)建者之間耦合度增強(qiáng)。舉例來(lái)說(shuō),如果在一個(gè)項(xiàng)目中我們需要?jiǎng)?chuàng)建一些圖形對(duì)象,例如Circle、Square。這些對(duì)象的結(jié)構(gòu)如下:
這個(gè)結(jié)構(gòu)是非常符合OO思想的,它通過(guò)IShape接口將Square和Circle對(duì)象抽象出來(lái),根據(jù)多態(tài)的原理,我們完全可以在程序中用IShape來(lái)代替具體的Square和Circle類,從而將具體的對(duì)象類型綁定留到運(yùn)行時(shí)。然而,上文說(shuō)到,接口對(duì)象是不能創(chuàng)建的,因此,項(xiàng)目一旦要?jiǎng)?chuàng)建IShape類型的對(duì)象,必然要針對(duì)具體的對(duì)象Square或Circle進(jìn)行創(chuàng)建操作。例如:
IShape shape = new Square();
如果是開(kāi)發(fā)一個(gè)圖形工具,諸如Square和Circle之類的對(duì)象,其創(chuàng)建工作必然非常頻繁??梢栽O(shè)想,在這個(gè)項(xiàng)目的各個(gè)模塊中,將會(huì)大量充斥著如上的代碼行,導(dǎo)致的結(jié)果是各個(gè)模塊無(wú)法與Square對(duì)象結(jié)耦,這意味著,如果我們改變創(chuàng)建的對(duì)象為Circle,就需要修改所有調(diào)用new Square()操作的模塊。這既加大了工作量,同時(shí)也導(dǎo)致了項(xiàng)目的不可擴(kuò)展性,以及模塊的不可重用性。而對(duì)于圖形對(duì)象的抽象IShape來(lái)說(shuō),也是不必要而失敗的。
在面向?qū)ο蟮脑O(shè)計(jì)中,我們常常將可能變化的操作進(jìn)行封裝,封裝的內(nèi)容可能僅是某種行為,也可能是一種狀態(tài),或者是某些職責(zé)。而在當(dāng)前的案例中,我們需要將對(duì)象的創(chuàng)建行為進(jìn)行封裝,這就引入了Factory Method模式。此時(shí)的對(duì)象就是Factory要生產(chǎn)的產(chǎn)品。既然產(chǎn)品有兩種,相對(duì)應(yīng)的工廠也應(yīng)該是兩個(gè),即SquareFactory和CircleFactory。在Factory Method模式中,工廠對(duì)象的結(jié)構(gòu)應(yīng)與產(chǎn)品的結(jié)構(gòu)平行,并與之一一對(duì)應(yīng),所以,對(duì)這兩個(gè)工廠而言,還需要為其抽象出一個(gè)共同的工廠接口IShapeFactory:
代碼如下:
public interface IShapeFactory
{
IShape CreateShape();
}
public class SquareFactory:IShapeFactory
{
public IShape CreateShape()
{
return new Square();
}
}
public class CircleFactory:IShapeFactory
{
public IShape CreateShape()
{
return new Circle();
}
}
通過(guò)Factory Method模式,我們完成了對(duì)象創(chuàng)建的封裝,將前面諸如IShape shape = new Square()的代碼全部移到了各自的工廠對(duì)象中,并放到CreateShape()方法中實(shí)現(xiàn)。整個(gè)結(jié)構(gòu)如下圖所示:
請(qǐng)注意CreateShape()方法的返回類型是IShape類型,這就有效地避免了工廠對(duì)象與具體產(chǎn)品對(duì)象的依賴。
也許會(huì)有人認(rèn)為,雖然通過(guò)工廠方法,將創(chuàng)建IShape對(duì)象的職責(zé)轉(zhuǎn)交給工廠對(duì)象,然而在工廠類的結(jié)構(gòu)中,仍然存在具體的工廠類對(duì)象。如此以來(lái),雖然我們解除了模塊與具體Shape對(duì)象的依賴,卻增加了對(duì)具體工廠對(duì)象的依賴,這會(huì)帶來(lái)何種益處?
讓我們從對(duì)象創(chuàng)建的頻率來(lái)分析。對(duì)于一個(gè)圖形工具而言,IShape對(duì)象的創(chuàng)建無(wú)疑是頻繁的,最大的可能性是在這個(gè)項(xiàng)目的各個(gè)模塊中都可能存在創(chuàng)建IShape對(duì)象的需要。而工廠對(duì)象則不盡然,我們完全可以集中在一個(gè)模塊中,初始化這個(gè)工廠對(duì)象,而在需要IShape對(duì)象的時(shí)候,直接調(diào)用工廠實(shí)例的CreateShape()就可以達(dá)到目的。
舉例來(lái)說(shuō),假設(shè)在圖形工具中,有三個(gè)模塊:ModuleA,ModuleB,ModuleC;這三個(gè)模塊中都需要?jiǎng)?chuàng)建Square對(duì)象,則按照原來(lái)的設(shè)計(jì)方案,這三個(gè)模塊都包含這樣一行代碼:
IShape shape = new Square();
此時(shí),與Square對(duì)象有依賴關(guān)系的就包括了ModuleA,ModuleB,ModuleC三個(gè)模塊。如果我們需要修改shape對(duì)象為Circle類型,則這個(gè)變動(dòng)無(wú)疑會(huì)影響到上述的三個(gè)模塊?,F(xiàn)在,我們引入Factory Method模式,并增加一個(gè)模塊名為ModuleFactory,在這個(gè)模塊中,我們創(chuàng)建一個(gè)工廠對(duì)象:
IShapeFactory shapeFactory = new SquareFactory();
如此以來(lái),原來(lái)的三個(gè)模塊有關(guān)Square對(duì)象的創(chuàng)建,就相應(yīng)地修改為:
IShape shape = shapeFactory.CreateShape();
此時(shí),即使需求發(fā)生改變,需要對(duì)shape對(duì)象進(jìn)行修改,那么我們只需要修改ModuleFactory模塊中的代碼:
IShapeFactory shapeFactory = new CircleFactory();
而ModuleA,ModuleB,ModuleC三個(gè)模塊則根本不需要作任何改變。如此的設(shè)計(jì)改進(jìn),雖然在項(xiàng)目中增加了三個(gè)工廠對(duì)象,并引入了ModuleFactory,但它卻完成了ModuleA,ModuleB,ModuleC與具體的Square對(duì)象的解耦,從而將這三個(gè)模塊與產(chǎn)品對(duì)象的依賴性轉(zhuǎn)嫁到ModuleFactory上。如此以來(lái),牽一發(fā)而不動(dòng)其全身,極大地提高了模塊的重用性。
從上述的分析可知,引入工廠對(duì)象并不是簡(jiǎn)單地為產(chǎn)品建立相應(yīng)的工廠,而是要注意劃分各個(gè)模塊的職責(zé),將工廠對(duì)象的創(chuàng)建放到合適的地方。最佳方案莫過(guò)于將創(chuàng)建工廠對(duì)象的職責(zé)集中起來(lái),放到一個(gè)模塊中;而不是在需要?jiǎng)?chuàng)建產(chǎn)品時(shí),才創(chuàng)建工廠對(duì)象。錯(cuò)誤的例子是在創(chuàng)建產(chǎn)品時(shí)將工廠對(duì)象的創(chuàng)建于產(chǎn)品對(duì)象的創(chuàng)建放在一起,并分布在各個(gè)模塊中:
IShapeFactory shapeFactory = new SquareFactory();
IShape shape = shapeFactory.CreateShape();
這樣的做法,則引入Factory Method模式,無(wú)異于畫(huà)蛇添足了。
二、.Net Framework中的Factory Method模式
Factory Method模式在項(xiàng)目設(shè)計(jì)中應(yīng)用非常廣泛,在.Net Framework中自然也不例外。例如,在.Net中為處理Web請(qǐng)求,在框架類庫(kù)中提供了基類WebRequest。它能夠通過(guò)傳入的Uri對(duì)象,創(chuàng)建基于不同協(xié)議的Web請(qǐng)求。例如,當(dāng)Uri的前綴為“https://”或“http://”時(shí),則返回HttpWebRequest對(duì)象,如果是“file://”,則返回FileWebRequest對(duì)象。HttpWebRequest和FileWebRequest對(duì)象是WebRequest的派生類。WebRequest的類結(jié)構(gòu)如圖:
如何創(chuàng)建一個(gè)WebRequest的實(shí)例呢?我們可以直接調(diào)用該類的靜態(tài)方法Create():
WebRequest myRequest = WebRequest.Create(“http://www.cnblogs.com”);
然后我們可以根據(jù)該WebRequest對(duì)象獲得WebResponse:
WebResponse myResponse = myRequest.GetResponse();
……
myResponse.Close();
從上面一段代碼來(lái)看,Create()靜態(tài)方法似乎是簡(jiǎn)單工廠模式的一種實(shí)現(xiàn),它可以根據(jù)方法傳遞進(jìn)來(lái)的參數(shù)判斷WebRequest的類型,創(chuàng)建具體的WebRequest對(duì)象,并返回該對(duì)象。那么最簡(jiǎn)單的實(shí)現(xiàn)方法,就是通過(guò)if/else條件判斷參數(shù)的類型,以決定創(chuàng)建的對(duì)象類型。顯然,這并非一個(gè)好的方法,它直接導(dǎo)致的就是WebRequest的具體子類與其靜態(tài)方法Create()直接依賴,一旦增加新的WebRequest子類,必然要修改Create()方法。
既然涉及到對(duì)象的創(chuàng)建,最好的方式就是使用Factory Method模式。在.Net中,為創(chuàng)建WebRequest對(duì)象提供了一個(gè)專門(mén)的工廠接口IWebRequestCreate,該接口僅有一個(gè)方法,即Create()方法:
public interface IWebRequestCreate
{
WebRequest Create(Uri uri);
}
對(duì)應(yīng)不同的Web請(qǐng)求,工廠模式為其提供了不同的具體工廠類,這些類均實(shí)現(xiàn)了IWebRequestCreate接口。例如HttpRequestCreator:
internal class HttpRequestCreator : IWebRequestCreate
{
internal HttpRequestCreator() {}
public WebRequest Create( Uri Uri )
{
return new HttpWebRequest(Uri);
}
}
還有類FileRequestCreator:
internal class FileWebRequestCreator : IWebRequestCreate
{
internal FileWebRequestCreator() {}
public WebRequest Create(Uri uri)
{
return new FileWebRequest(uri);
}
}
這些類都實(shí)現(xiàn)了接口IWebRequestCreate的Create()方法,返回各自對(duì)應(yīng)的WebRequest對(duì)象,即工廠模式所要生產(chǎn)的產(chǎn)品。
這樣,我們就為產(chǎn)品類建立了一個(gè)和其完全一一對(duì)應(yīng)的工廠類結(jié)構(gòu):
請(qǐng)注意工廠類和產(chǎn)品類之間,只存在接口IWebRequestCreate和抽象類WebRequest之間的依賴關(guān)系,這正是OOP中面向抽象編程的實(shí)質(zhì)。
三、進(jìn)一步探索
根據(jù)前面的描述,當(dāng)我們?cè)趧?chuàng)建WebRequest對(duì)象時(shí),需要在系統(tǒng)中預(yù)先創(chuàng)建其對(duì)應(yīng)的工廠類對(duì)象。如下所示:
IWebRequestCreate webRequestCreate = new HttpRequestCreator();
然而與一般對(duì)象不同的是,WebRequest的類型是隨時(shí)可能變化的,這就導(dǎo)致其對(duì)應(yīng)的工廠類型也會(huì)經(jīng)常改變。如果將上面的代碼寫(xiě)到一個(gè)專門(mén)的模塊中,并供客戶端修改,會(huì)缺乏一定的靈活性。并且對(duì)于客戶而言,WebRequest對(duì)象的創(chuàng)建實(shí)在太麻煩了。因?yàn)?Net Framework是一個(gè)類庫(kù),而類庫(kù)的設(shè)計(jì)理念,就是要盡可能讓用戶更容易更方便地使用類庫(kù),至于內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),用戶是不用理會(huì)的。因此,.Net對(duì)Factory Method模式進(jìn)行了一些加工。下面,我將對(duì)WebReuest的創(chuàng)建過(guò)程基于.Net的實(shí)現(xiàn)進(jìn)行深入分析。
前面提到,WebRequest是一個(gè)抽象類,但它提供了一個(gè)靜態(tài)方法Create(),能夠根據(jù)方法中傳入的Uri地址,創(chuàng)建相對(duì)應(yīng)的WebRequest對(duì)象。它的實(shí)現(xiàn)代碼如下:
public static WebRequest Create(Uri requestUri)
{
if (requestUri == null) {
throw new ArgumentNullException(”requestUri”);
}
return Create(requestUri, false);
}
該方法實(shí)質(zhì)是調(diào)用了WebRequest中的私有靜態(tài)方法Create():
private static WebRequest Create(Uri requestUri, bool useUriBase)
{
string LookupUri;
WebRequestPrefixElement Current = null;//①
bool Found = false;
if (!useUriBase) {
LookupUri = requestUri.AbsoluteUri;
}
else {
LookupUri = requestUri.Scheme + ‘:’;
}
int LookupLength = LookupUri.Length;
ArrayList prefixList = PrefixList; //②
for (int i = 0; i < prefixList.Count; i++)
{
Current = (WebRequestPrefixElement)prefixList[i]; //④
// See if this prefix is short enough.
if (LookupLength >= Current.Prefix.Length)
{
// It is. See if these match.
if (String.Compare(Current.Prefix,0,LookupUri,0,Current.Prefix.Length,true,CultureInfo.InvariantCulture) == 0)
{
Found = true;
break;
}
}
}
if (Found)
{
return Current.Creator.Create(requestUri); //③
}
throw new NotSupportedException(SR.GetString(SR.net_unknown_prefix));
}
注意該方法中被我標(biāo)注的幾行代碼。第①行代碼定義了一個(gè)WebRequestPrefixElement對(duì)象Current,第②行代碼則定義了一個(gè)ArrayList對(duì)象,并將一個(gè)已經(jīng)存在的ArrayList對(duì)象PrefixList賦給它。第③行代碼則通過(guò)Current對(duì)象的Creator字段來(lái)完成創(chuàng)建工作。整體來(lái)講,該方法就是在一個(gè)ArrayList中,根據(jù)參數(shù)Uri的值進(jìn)行查找,如果找到,則創(chuàng)建相關(guān)的對(duì)象并返回,否則拋出異常。
現(xiàn)在我們需要明白兩個(gè)問(wèn)題:
1、WebRequestPrefixElement類的定義什么?而該類型的Creator字段又屬于什么類型?
2、PrefixList對(duì)象存儲(chǔ)的內(nèi)容是什么?
我們首先看看WebRequestPrefixElement類的定義:
internal class WebRequestPrefixElement
{
public string Prefix;
public IWebRequestCreate Creator;
public WebRequestPrefixElement(string P, IWebRequestCreate C)
{
Prefix = P;
Creator = C;
}
}
很顯然,該類僅僅是提供一個(gè)將Uri前綴與IWebRequestCreate類型關(guān)聯(lián)的一個(gè)簡(jiǎn)單對(duì)象而已。而Creator字段的類型正是IWebRequestCreate類型。Uri和IWebRequestCreate的關(guān)聯(lián)對(duì)象,可通過(guò)WebRequestPrefixElement的構(gòu)造函數(shù)來(lái)傳入。
那么,Current.Creator.Create(requestUri)創(chuàng)建對(duì)象的實(shí)質(zhì),就是通過(guò)調(diào)用IWebRequestCreate類型對(duì)象的工廠方法,來(lái)完成對(duì)WebRequest對(duì)象的創(chuàng)建。然而,究竟調(diào)用的是哪一個(gè)具體工廠類呢?也就是Current字段,其代表的IWebRequestCreate對(duì)象究竟是什么?
根據(jù)第④行代碼,Current的值是從prefixList列表中獲得的IWebRequestCreate對(duì)象。而prefixList值在該方法中就是PrefixList對(duì)象。而PrefixList其實(shí)是WebRequest類的一個(gè)私有屬性:
private static ArrayList PrefixList
{
get
{
if (s_PrefixList == null)
{
lock (typeof(WebRequest))
{
if (s_PrefixList == null)
{
GlobalLog.Print(”WebRequest::Initialize(): calling ConfigurationSettings.GetConfig()”);
ArrayList prefixList = (ArrayList)ConfigurationSettings.GetConfig(”system.net/webRequestModules”);
if (prefixList == null)
{
GlobalLog.Print(”WebRequest::Initialize(): creating default settings”);
HttpRequestCreator Creator = new HttpRequestCreator();
// longest prefixes must be the first
prefixList = new ArrayList();
prefixList.Add(new WebRequestPrefixElement(”https”, Creator)); // [0]
prefixList.Add(new WebRequestPrefixElement(”http”, Creator)); // [1]
prefixList.Add(new WebRequestPrefixElement(”file”, new FileWebRequestCreator())); // [2]
}
s_PrefixList = prefixList;
}
}
}
return s_PrefixList;
}
set
{
s_PrefixList = value;
}
}
PrefixList屬性的Get訪問(wèn)器中,進(jìn)行了一系列的判斷以及初始化工作,其中最重要的工作則是在其內(nèi)部自動(dòng)添加了三個(gè)元素,均為WebRequestPrefixElement對(duì)象,而通過(guò)該對(duì)象,在prefixList中建立了Uri和IWebRequestCreate之間的關(guān)系:
prefixList.Add(new WebRequestPrefixElement(”https”, Creator)); // [0]
prefixList.Add(new WebRequestPrefixElement(”http”, Creator)); // [1]
prefixList.Add(new WebRequestPrefixElement(”file”, new FileWebRequestCreator())); // [2]
前兩個(gè)對(duì)象中,工廠類型均為HttpWebRequestCreator,而第三個(gè)對(duì)象則為FileWebRequestCreator。這正好是.Net提供的兩種繼承WebRequest的具體工廠類。
調(diào)用WebRequest的靜態(tài)方法Create()時(shí),系統(tǒng)會(huì)根據(jù)傳入的Uri對(duì)象,在prefixList中搜索前綴與PrefixList列表中的uri匹配的WebRequestPrefixElement類型對(duì)象(通過(guò)String.Compare()方法)。如果找到,則根據(jù)一一映射的關(guān)系,去調(diào)用對(duì)應(yīng)的工廠類,創(chuàng)建相應(yīng)的Web請(qǐng)求實(shí)例,即Create()方法中的第③行代碼。
再回過(guò)頭來(lái)看創(chuàng)建WebRequest對(duì)象的代碼:
WebRequest myRequest = WebRequest.Create(“http://www.cnblogs.com”);
根據(jù)前面分析的過(guò)程,這行代碼封裝的內(nèi)部實(shí)現(xiàn)應(yīng)該是如下步驟:
1、將字符串”http://www.cnblogs.com”傳遞到WebRequest類的靜態(tài)私有方法Create()中;
2、將WebRequest的私有屬性PrefixList值賦給方法內(nèi)的局部變量prefixList對(duì)象。此時(shí)會(huì)調(diào)用PrefixList的Get訪問(wèn)器。該訪問(wèn)器會(huì)初始化PrefixList對(duì)象,將默認(rèn)的Uri和IWebRequestCreate類型的值添加到PrefixList中;
3、解析傳入的Uri,得到值”http”,對(duì)應(yīng)的IWebRequestCreate對(duì)象為HttpWebRequestCreator對(duì)象;
4、調(diào)用HttpWebRequestCreator對(duì)象的Create()方法,創(chuàng)建HttpWebRequest對(duì)象,并返回。
執(zhí)行步驟的時(shí)序圖如下:
現(xiàn)在,考慮擴(kuò)展的情況。如果此時(shí)派生于WebRequest類的不僅僅有HttpWebRequest和FileWebRequest子類,還有新增加的其他子類,例如FtpRequest類,它對(duì)應(yīng)的Uri為”ftp”。對(duì)應(yīng)的工廠類為FtpWebRequestCreator。此時(shí)應(yīng)該如何創(chuàng)建?根據(jù)前面的分析來(lái)看,由于PrefixList的Get訪問(wèn)器中,并沒(méi)有添加其他的WebRequestPrefixElement對(duì)象,如果輸入U(xiǎn)ri前綴”ftp”,Create()方法是無(wú)法找到合適的IWebRequestCreate工廠對(duì)象的。難道,當(dāng)我們擴(kuò)展新的WebRequest子類時(shí),還是需要修改WebRequest類中的Prefix屬性代碼嗎?如果真是這樣,前面引入的工廠類,以及許多設(shè)計(jì)就顯得拙劣可笑了。
顯然,這種可笑的錯(cuò)誤,.Net Framework的設(shè)計(jì)師們是不可能犯下的。事實(shí)上,.Net在WebRequest的創(chuàng)建過(guò)程中,引入了WebRequestPrefixElement類以及ArrayList對(duì)象PrefixList,就已經(jīng)提示我們,F(xiàn)ramework已經(jīng)考慮到了擴(kuò)展的可能。因?yàn)?,ArrayList對(duì)象是允許我們動(dòng)態(tài)加入對(duì)象的。事實(shí)正是如此,在WebRequest類中,還提供了一個(gè)注冊(cè)WebRequestPrefixElement的公共靜態(tài)方法RegisterPrefix():
public static bool RegisterPrefix(string prefix, IWebRequestCreate creator)
{
bool Error = false;
int i;
WebRequestPrefixElement Current;
if (prefix == null)
{
throw new ArgumentNullException(”prefix”);
}
if (creator == null)
{
throw new ArgumentNullException(”creator”);
}
lock(typeof(WebRequest))
{
ArrayList prefixList = (ArrayList) PrefixList.Clone();
i = 0;
while (i < prefixList.Count)
{
Current = (WebRequestPrefixElement)prefixList[i];
if (prefix.Length > Current.Prefix.Length)
{
// It is. Break out of the loop here.
break;
}
if (prefix.Length == Current.Prefix.Length)
{
// They’re the same length.
if (String.Compare(Current.Prefix, prefix, true, CultureInfo.InvariantCulture) == 0)
{
// …and the strings are identical. This is an error.
Error = true;
break;
}
}
i++;
}
if (!Error)
{
prefixList.Insert(i, new WebRequestPrefixElement(prefix, creator));
PrefixList = prefixList;
}
}
return !Error;
}
只要在已有的PrefixList中沒(méi)有找到參數(shù)中傳遞進(jìn)來(lái)的prefix值,就將新的prefix值和IWebRequestCreate對(duì)象插入到PrefixList中。通過(guò)該方法,就可以動(dòng)態(tài)添加新的工廠類了。例如:
WebRequest.RegisterPrefix(“ftp”,new FtpWebRequestCreator());
WebRequest ftpRequest = WebRequest.Create(“ftp://www.cnblogs.com”);
通過(guò)這種實(shí)現(xiàn)方式,就可以解開(kāi)具體工廠類、具體產(chǎn)品類與WebRequest靜態(tài)方法Create()之間的耦合。
在.Net Framework中,關(guān)于WebRequest類對(duì)象的創(chuàng)建,其實(shí)現(xiàn)方式雖然看似復(fù)雜,但其本質(zhì)仍然屬于Factory Method模式。但為了類庫(kù)的更易于使用,并考慮到通用性和擴(kuò)展性,又引入了類似于映射的WebRequestPrefixElement類以及ArrayList對(duì)象,同時(shí)又將具體工廠類對(duì)象設(shè)定為internal,并包裝到抽象產(chǎn)品基類WebRequest的靜態(tài)方法中。這種設(shè)計(jì)方法是我們?cè)趹?yīng)用Factory Method模式設(shè)計(jì)自己的類庫(kù)時(shí),值得借鑒的。




