——探索設(shè)計(jì)模式系列之五
Terrylee,2004年1月2日
概述
在軟件系統(tǒng)中,經(jīng)常面臨著“某個(gè)對(duì)象”的創(chuàng)建工作,由于需求的變化,這個(gè)對(duì)象的具體實(shí)現(xiàn)經(jīng)常面臨著劇烈的變化,但是它卻擁有比較穩(wěn)定的接口。如何應(yīng)對(duì)這種變化?提供一種封裝機(jī)制來(lái)隔離出“這個(gè)易變對(duì)象”的變化,從而保持系統(tǒng)中“其它依賴(lài)該對(duì)象的對(duì)象”不隨著需求的改變而改變?這就是要說(shuō)的Factory Method模式了。
意圖
定義一個(gè)用戶(hù)創(chuàng)建對(duì)象的接口,讓子類(lèi)決定實(shí)例化哪一個(gè)類(lèi)。Factory Method使一個(gè)類(lèi)的實(shí)例化延遲到其子類(lèi)。
結(jié)構(gòu)圖
生活中的例子
工廠方法定義一個(gè)用于創(chuàng)建對(duì)象的接口,但是讓子類(lèi)決定實(shí)例化哪個(gè)類(lèi)。壓注成型演示了這種模式。塑料玩具制造商加工塑料粉,將塑料注入到希望形狀的模具中。玩具的類(lèi)別(車(chē),人物等等)是由模具決定的。
工廠方法解說(shuō)
在工廠方法模式中,核心的工廠類(lèi)不再負(fù)責(zé)所有產(chǎn)品的創(chuàng)建,而是將具體創(chuàng)建工作交給子類(lèi)去做。這個(gè)核心類(lèi)僅僅負(fù)責(zé)給出具體工廠必須實(shí)現(xiàn)的接口,而不接觸哪一個(gè)產(chǎn)品類(lèi)被實(shí)例化這種細(xì)節(jié)。這使得工廠方法模式可以允許系統(tǒng)在不修改工廠角色的情況下引進(jìn)新產(chǎn)品。在Factory Method模式中,工廠類(lèi)與產(chǎn)品類(lèi)往往具有平行的等級(jí)結(jié)構(gòu),它們之間一一對(duì)應(yīng)。
現(xiàn)在我們考慮一個(gè)日志記錄的例子(這里我們只是為了說(shuō)明Factory Method模式,實(shí)際項(xiàng)目中的日志記錄不會(huì)這么去做,也要比這復(fù)雜一些)。假定我們要設(shè)計(jì)日志記錄的類(lèi),支持記錄的方法有FileLog和EventLog兩種方式。在這里我們先不談設(shè)計(jì)模式,那么這個(gè)日志記錄的類(lèi)就很好實(shí)現(xiàn)了:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
進(jìn)一步抽象,為它們抽象出一個(gè)共同的父類(lèi),結(jié)構(gòu)圖如下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
實(shí)現(xiàn)代碼: 此時(shí)EventLog
2
3
4
5
6
7
8
此時(shí)我們?cè)倏丛黾有碌挠涗浫罩痉绞?/span>DatabaseLog的時(shí)候,需要做哪些事情?只需要增加一個(gè)繼承父類(lèi)Log的子類(lèi)來(lái)實(shí)現(xiàn),而無(wú)需再去修改EventLog和FileLog類(lèi),這樣的設(shè)計(jì)滿(mǎn)足了類(lèi)之間的層次關(guān)系,又很好的符合了面向?qū)ο笤O(shè)計(jì)中的單一職責(zé)原則,每一個(gè)類(lèi)都只負(fù)責(zé)一件具體的事情。到這里似乎我們的設(shè)計(jì)很完美了,事實(shí)上我們還沒(méi)有看客戶(hù)程序如何去調(diào)用。 在應(yīng)用程序中,我們要使用某一種日志記錄方式,也許會(huì)用到如下這樣的語(yǔ)句: 當(dāng)日志記錄的方式從 這兩個(gè)工廠和具體的產(chǎn)品之間是平行的結(jié)構(gòu),并一一對(duì)應(yīng),并在它們的基礎(chǔ)上抽象出一個(gè)公用的接口,結(jié)構(gòu)圖如下: 實(shí)現(xiàn)代碼如下: 此時(shí)兩個(gè)具體工廠的代碼應(yīng)該如下: 這樣通過(guò)工廠方法模式我們把上面那對(duì)象創(chuàng)建工作封裝在了工廠中,此時(shí)我們似乎完成了整個(gè) 在客戶(hù)程序中,我們有效地避免了具體產(chǎn)品對(duì)象和應(yīng)用程序之間的耦合,可是我們也看到,增加了具體工廠對(duì)象和應(yīng)用程序之間的耦合。那這樣究竟帶來(lái)什么好處呢?我們知道,在應(yīng)用程序中, LogFactory factory = new EventFactory(); 這句話(huà)放在一個(gè)類(lèi)模塊中,任何需要用到Log對(duì)象的地方仍然不變。要是換一種日志記錄方式,只要修改一處為: LogFactory factory = new FileFactory(); 其余的任何地方我們都不需要去修改。有人會(huì)說(shuō)那還是修改代碼,其實(shí)在開(kāi)發(fā)中我們很難避免修改,但是我們可以盡量做到只修改一處。 其實(shí)利用.NET的特性,我們可以避免這種不必要的修改。下面我們利用.NET中的反射機(jī)制來(lái)進(jìn)一步修改我們的程序,這時(shí)就要用到配置文件了,如果我們想使用哪一種日志記錄方式,則在相應(yīng)的配置文件中設(shè)置如下: 此時(shí)客戶(hù)端代碼如下: 現(xiàn)在我們看到,在引進(jìn)新產(chǎn)品(日志記錄方式)的情況下,我們并不需要去修改工廠類(lèi),而只是增加新的產(chǎn)品類(lèi)和新的工廠類(lèi)(注意:這是任何時(shí)候都不能避免的),這樣很好的符合了開(kāi)放封閉原則。 ASP.NET HTTP通道中的應(yīng)用 Factory Method模式在ASP.NET HTTP通道中我們可以找到很多的例子。ASP.NET HTTP通道是System.Web命名空間下的一個(gè)類(lèi),WEB Server使用該類(lèi)處理接收到的HTTP請(qǐng)求,并給客戶(hù)端發(fā)送響應(yīng)。HTTP通道主要的工作有Session管理,應(yīng)用程序池管理,緩存管理,安全等。 System.Web.HttpApplicationFactory HttpRuntime是HTTP通道的入口點(diǎn),它根據(jù)每一個(gè)具體的請(qǐng)求創(chuàng)建一個(gè)HttpContext實(shí)例, HttpRuntime并沒(méi)有確定它將要處理請(qǐng)求的HttpApplication對(duì)象的類(lèi)型,它調(diào)用了一個(gè)靜態(tài)的工廠方法HttpApplicationFactory.GetApplicationInstance,通過(guò)它來(lái)創(chuàng)建HttpContext實(shí)例。GetApplicationInstance使用HttpContext實(shí)例來(lái)確定針對(duì)這個(gè)請(qǐng)求該響應(yīng)哪個(gè)虛擬路徑,如果這個(gè)虛擬路徑以前請(qǐng)求過(guò),HttpApplication(或者一個(gè)繼承于ASP.Global_asax的類(lèi)的實(shí)例)將直接從應(yīng)用程序池中返回,否則針對(duì)該虛擬路徑將創(chuàng)建一個(gè)新的HttpApplication對(duì)象并返回。如下圖所示: HttpApplicationFactory.GetApplicationInstance帶有一個(gè)類(lèi)型為HttpContext的參數(shù),創(chuàng)建的所有對(duì)象(產(chǎn)品)都是HttpApplication的類(lèi)型,通過(guò)反編譯,來(lái)看一下GetApplicationInstance的實(shí)現(xiàn): 我們來(lái)做進(jìn)一步的探索,HttpApplication實(shí)例需要一個(gè)Handler對(duì)象來(lái)處理資源請(qǐng)求, HttpApplication的主要任務(wù)就是找到真正處理請(qǐng)求的類(lèi)。HttpApplication首先確定了一個(gè)創(chuàng)建Handler對(duì)象的工廠,來(lái)看一下在Machine.config文件中的配置區(qū)<httphandlers>,在配置文件注冊(cè)了應(yīng)用程序的具體處理類(lèi)。例如在Machine.config中對(duì)*.aspx的處理將映射到System.Web.UI.PageHandlerFactory 類(lèi),而對(duì)*.ashx的處理將映射到System.Web.UI.SimpleHandlerFactory 類(lèi),這兩個(gè)類(lèi)都是繼承于IhttpHandlerFactory接口的具體類(lèi): 這個(gè)配置區(qū)建立了資源請(qǐng)求的類(lèi)型和處理請(qǐng)求的類(lèi)之間的一個(gè)映射集。如果一個(gè) IHttpHandlerFactory工廠: 抽象工廠角色:IHttpHandlerFactory 具體工廠角色:PageHandlerFactory 抽象產(chǎn)品角色:IHttpHandler 具體產(chǎn)品角色:ASP.SamplePage_aspx 進(jìn)一步去理解 理解上面所說(shuō)的之后,我們就可以去自定義工廠類(lèi)來(lái)對(duì)特定的資源類(lèi)型進(jìn)行處理。第一步我們需要?jiǎng)?chuàng)建兩個(gè)類(lèi)去分別實(shí)現(xiàn)IHttpHandlerFactory 和IHttpHandler這兩個(gè)接口。 第二步需要在配置文件中建立資源請(qǐng)求類(lèi)型和處理程序之間的映射。我們希望當(dāng)請(qǐng)求的類(lèi)型為 最后一步我們需要把文件擴(kuò)展 運(yùn)行Internet服務(wù)管理器,右鍵點(diǎn)擊默認(rèn)Web站點(diǎn),選擇屬性,移動(dòng)到主目錄選項(xiàng)頁(yè),并點(diǎn)擊配置按鈕。應(yīng)用程序配置對(duì)話(huà)框彈出來(lái)了。點(diǎn)擊添加按鈕并在可執(zhí)行字段輸入aspnet_isapi.dll文件路徑,在擴(kuò)展字段輸入.sample。其它字段不用處理;該對(duì)話(huà)框如下所示: 在.NET Framework中,關(guān)于工廠模式的使用有很多的例子,例如IEnumerable和IEnumerator就是一個(gè)Creator和一個(gè)Product;System.Security.Cryptography中關(guān)于加密算法的選擇,SymmetricAlgorithm, AsymmetricAlgorithm, 和HashAlgorithm分別是三個(gè)工廠,他們各有一個(gè)靜態(tài)的工廠方法Create;System.Net.WebRequest是 .NET Framework 的用于訪問(wèn) Internet 數(shù)據(jù)的請(qǐng)求/響應(yīng)模型的抽象基類(lèi)。使用該請(qǐng)求/響應(yīng)模型的應(yīng)用程序可以用協(xié)議不可知的方式從 Internet 請(qǐng)求數(shù)據(jù)。在這種方式下,應(yīng)用程序處理 WebRequest 類(lèi)的實(shí)例,而協(xié)議特定的子類(lèi)則執(zhí)行請(qǐng)求的具體細(xì)節(jié)。請(qǐng)求從應(yīng)用程序發(fā)送到某個(gè)特定的 URI,如服務(wù)器上的 Web 頁(yè)。URI 從一個(gè)為應(yīng)用程序注冊(cè)的 WebRequest 子代列表中確定要?jiǎng)?chuàng)建的適當(dāng)子類(lèi)。注冊(cè) WebRequest 子代通常是為了處理某個(gè)特定的協(xié)議(如 HTTP 或 FTP),但是也可以注冊(cè)它以處理對(duì)特定服務(wù)器或服務(wù)器上的路徑的請(qǐng)求。有時(shí)間我會(huì)就.NET Framework中工廠模式的使用作一個(gè)專(zhuān)題總結(jié)。 實(shí)現(xiàn)要點(diǎn) 1. Factory Method模式的兩種情況:一是Creator類(lèi)是一個(gè)抽象類(lèi)且它不提供它所聲明的工廠方法的實(shí)現(xiàn);二是Creator是一個(gè)具體的類(lèi)且它提供一個(gè)工廠方法的缺省實(shí)現(xiàn)。 2. 工廠方法是可以帶參數(shù)的。 3. 工廠的作用并不僅僅只是創(chuàng)建一個(gè)對(duì)象,它還可以做對(duì)象的初始化,參數(shù)的設(shè)置等。 效果 1. 用工廠方法在一個(gè)類(lèi)的內(nèi)部創(chuàng)建對(duì)象通常比直接創(chuàng)建對(duì)象更靈活。 2. Factory Method模式通過(guò)面向?qū)ο蟮氖址?,將所要?jiǎng)?chuàng)建的具體對(duì)象的創(chuàng)建工作延遲到了子類(lèi),從而提供了一種擴(kuò)展的策略,較好的解決了這種緊耦合的關(guān)系。 適用性 在以下情況下,適用于工廠方法模式: 1. 當(dāng)一個(gè)類(lèi)不知道它所必須創(chuàng)建的對(duì)象的類(lèi)的時(shí)候。 2. 當(dāng)一個(gè)類(lèi)希望由它的子類(lèi)來(lái)指定它所創(chuàng)建的對(duì)象的時(shí)候。 3. 當(dāng)類(lèi)將創(chuàng)建對(duì)象的職責(zé)委托給多個(gè)幫助子類(lèi)中的某一個(gè),并且你希望將哪一個(gè)幫助子類(lèi)是代理者這一信息局部化的時(shí)候。 總結(jié) Factory Method模式是設(shè)計(jì)模式中應(yīng)用最為廣泛的模式,通過(guò)本文,相信讀者已經(jīng)對(duì)它有了一定的認(rèn)識(shí)。然而我們要明確的是:在面向?qū)ο蟮木幊讨?,?duì)象的創(chuàng)建工作非常簡(jiǎn)單,對(duì)象的創(chuàng)建時(shí)機(jī)卻很重要。Factory Method要解決的就是對(duì)象的創(chuàng)建時(shí)機(jī)問(wèn)題,它提供了一種擴(kuò)展的策略,很好地符合了開(kāi)放封閉原則。__________________________________________________________________________________ 參考文獻(xiàn): 《設(shè)計(jì)模式》(中文版) MSDN:《Exploring the Factory Design Pattern》 《DesignPatternsExplained》
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
聯(lián)系客服