.NET(C#) Internals: .NET Framework中已使用的设计模式
——適合有一定設(shè)計(jì)模式基礎(chǔ)和.NET基礎(chǔ)的人閱讀。
寫在前面
“設(shè)計(jì)模式”我一向是敬而遠(yuǎn)之的態(tài)度,不會去寫這方面的文章,原因有二:第一,要想寫好設(shè)計(jì)模式的文章太難,需要筆者豐富的經(jīng)驗(yàn);第二,沒有深厚的 功底寫出的設(shè)計(jì)模式文章容易誤導(dǎo)他人。自認(rèn)沒有深厚的功底,但我不會為了設(shè)計(jì)模式而設(shè)計(jì)模式。我想大部分人對設(shè)計(jì)模式的理解是不夠深刻的,不然應(yīng)用自如, 特別是初學(xué)者!所有研究高質(zhì)量的源碼或框架是我們學(xué)習(xí)實(shí)踐設(shè)計(jì)模式的好途徑之一。
而我之所以寫這篇文章,主要是因?yàn)樗鼜?NET Framework入手介紹已經(jīng)使用的設(shè)計(jì)模式,作為一個(gè).NET開發(fā)人員應(yīng)該再熟悉不過了,能夠有比較深刻的認(rèn)識和理解。本文從.NET Framework中入手,發(fā)掘在.NET Framework中如何使用設(shè)計(jì)模式的。從中我們知道我們平時(shí)使用.NET時(shí),我們使用了那些模式及學(xué)習(xí)使用設(shè)計(jì)模式。本文意譯自Discover the Design Patterns You're Already Using in the .NET Framework及加入了相關(guān)設(shè)計(jì)模式的UML表示和主要介紹。
主要內(nèi)容如下:
- .NET Framework中使用的觀察者模式(Observer Pattern)
- .NET Framework中使用的迭代器模式(Iterator Pattern)
- .NET Framework中使用的裝飾模式(Decorator Pattern)
- .NET Framework中使用的適配器模式(Adapter Pattern)
- .NET Framework中使用的工廠模式(Factory Pattern)
- .NET Framework中使用的策略模式(Strategy Pattern)
- ASP.NET中的組合模式(Composite Pattern)
- .NET Framework中使用的模板方法模式(Template Method Pattern)
- ASP.NET管道中的模式(Patterns in the ASP.NET Pipeline)
- 截取過濾器模式(Intercepting Filter Pattern)
- 頁面控制器模式(Page Controller Pattern)
- ASP.NET中的其它web表示模式(Other Web Presentation Patterns in ASP.NET)
- 總結(jié)
1、觀察者模式(Observer Pattern)
觀察者模式:在此種模式中,一個(gè)目標(biāo)物件管理所有相依于它的觀察者物件,并且在它本身的狀態(tài)改變時(shí)主動(dòng)發(fā)出通知。這通常通過調(diào)用各觀察者所提供的方法來實(shí)現(xiàn)。它的UML表示如下:
圖1、觀察者模式的UML表示 (來源:維基百科)
好的面向?qū)ο笤O(shè)計(jì)都強(qiáng)調(diào)封裝(encapsulation)和松耦合(loose coupling)。換句話說,類應(yīng)該保持內(nèi)部細(xì)節(jié)私有并且最小化類之間嚴(yán)格的依賴關(guān)系。大部分應(yīng)用程序,類并不是獨(dú)立工作的,而是與其他類交互的。類交 互的一個(gè)通常例子是:一個(gè)類應(yīng)該(觀察者,Observer)被通知,當(dāng)被觀察者(Subject)的某些東西改變了。例如,當(dāng)單擊一個(gè)按鈕后可能某些 Windows Forms的控件需要更新他們的顯示。一個(gè)簡單的解決方案是,當(dāng)狀態(tài)改變時(shí)讓被觀察者調(diào)用觀察者特定的方法。但是,這回引入一連串的問題。因?yàn)楸挥^察者需 要知道調(diào)用哪個(gè)方法,這樣就與特定觀察者產(chǎn)生了緊耦合(tight coupling)。而且,如果當(dāng)需要添加多個(gè)觀察者時(shí),不得不繼續(xù)添加每個(gè)觀察者方法調(diào)用的代碼。如果觀察者的數(shù)量動(dòng)態(tài)地改變,這將變得更復(fù)雜。這將很 難維護(hù)!
應(yīng)用觀察者模式能有效地解決這個(gè)問題。可以從觀察者解耦被觀察者,因此在設(shè)計(jì)時(shí)和運(yùn)行時(shí)觀察者可以容易地添加和移除。被觀察者維護(hù)者一個(gè)對它感興趣的觀察者列表,每次被觀察者的狀態(tài)改變時(shí),它對每個(gè)觀察者調(diào)用Notify方法。下面這段代碼展示了一個(gè)實(shí)現(xiàn)示例:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public abstract class CanonicalSubjectBase { ????private ArrayList _observers = new ArrayList(); ????public void Add(ICanonicalObserver o) ????{ ????????_observers.Add(o); ????} ????public void Remove(ICanonicalObserver o) ????{ ????????_observers.Remove(o); ????} ????public void Notify() ????{ ????????foreach(ICanonicalObserver o in _observers) ????????{ ????????????o.Notify(); ????????} ????} } public interface ICanonicalObserver { ????void Notify(); } |
所有的觀察者類實(shí)現(xiàn)ICanonicalObserver接口,所有的被觀察者必須繼承自CanonicalSubjectBase。如果一個(gè)新的觀察者想監(jiān)視被觀察者,Add方法可以輕松的處理而不必改變被觀察者類的代碼。注意:每個(gè)被觀察者僅僅直接依賴于ICanonicalObserver接口,而不是特定的觀察者。
然而使用GOF的觀察者模式解決這些問題仍有一些障礙,因?yàn)楸挥^察者必須繼承一個(gè)特定的基類且觀察者必須實(shí)現(xiàn)一個(gè)特定接口。考慮回Windows Forms按鈕的例子,.NET Framework引入了委托和事件來解決這些問題。如果你已經(jīng)編寫過ASP.NET或Windows Forms程序,你可能就是有了事件和事件處理器。事件作為被觀察者,然而委托作為觀察者。下面代碼展示了使用事件的觀察者模式:
| 1 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 35 36 37 | public delegate void Event1Hander(); public delegate void Event2Handler(int a); public class Subject { ????public Subject(){} ????public Event1Hander Event1; ????public Event2Handler Event2; ????public void RaiseEvent1() ????{ ????????Event1Handler ev = Event1; ????????if (ev != null) ev(); ?????} ????public void RaiseEvent2() ????{ ????????Event2Handler ev = Event2; ????????if (ev != null) ev(6); ????} } public class Observer1 { ????public Observer1(Subject s) ????{ ????????s.Event1 += new Event1Hander(HandleEvent1); ????????s.Event2 += new Event2Handler(HandleEvent2); ????} ????public void HandleEvent1() ????{ ????????Console.WriteLine("Observer 1 - Event 1"); ????} ????public void HandleEvent2(int a) ????{ ????????Console.WriteLine("Observer 1 - Event 2"); ????} } |
Windows Forms Button控件公開一個(gè)Click事件,當(dāng)button被點(diǎn)擊時(shí)產(chǎn)生。任何設(shè)計(jì)為響應(yīng)這個(gè)事件的類僅需要用這個(gè)事件注冊一個(gè)委托。Button類不依賴 與任何潛在的觀察者,并且每個(gè)觀察者僅需要知道這個(gè)事件的委托的正確類型(這里是EventHandler)。因?yàn)镋ventHandler是一個(gè)委托類 型而不是一個(gè)接口,每個(gè)觀察者不需要實(shí)現(xiàn)一個(gè)額外的接口。假定它已經(jīng)包含一個(gè)與簽名兼容的方法,只需要用被觀察者的事件注冊方法。通過使用委托和事件,觀 察者模式使被觀察者與觀察者們之間解耦了。
2、迭代器模式(Iterator Pattern)
迭代器模式:它可以讓使用者通過特定的接口巡訪容器中的每一個(gè)元素而不用了解底層的實(shí)作。它的UML表示如下:
圖2、迭代器模式的UML表示(來源:TerryLee的.NET 設(shè)計(jì)模式(18):迭代器模式(Iterator Pattern))
許多編程任務(wù)包括操作對象的集合。不管這些集合是簡單的列表還是更復(fù)雜的,如二叉樹,經(jīng)常需要訪問集合中的每個(gè)對象。事實(shí)上,根據(jù)集合可能有幾種不同的訪問每個(gè)對象的方法,諸如從前向后、從后向前、前序或后序。為了保持集合簡單,遍歷代碼通常放在自己單獨(dú)的類中。
存儲一個(gè)對象列表的常用方法之一就是用數(shù)組。數(shù)組類型在Visual Basic.NET和C#中都是內(nèi)置類型,他們都有一個(gè)循環(huán)結(jié)構(gòu)用于在數(shù)組上迭代:foreach(C#)和For Each(Visual Basic.NET)。下面是一個(gè)在數(shù)組上進(jìn)行迭代的簡單例子:
| 1 2 3 4 5 6 | int[] values = new int[] {1, 2, 3, 4, 5}; foreach(int i in values) { ????Console.Write(i.ToString() + " "); } |
這些語句在后臺對數(shù)組使用了迭代器。我們需要知道的就是它保證了循環(huán)保證了對數(shù)組中的每個(gè)元素進(jìn)行一次遍歷。
為了使這些語句起作用,foreach表達(dá)式中涉及的對象必須實(shí)現(xiàn)了IEnumerable接口。任何實(shí)現(xiàn)了IEnumerable接口的對象集合都可以被遍歷(枚舉)。這個(gè)接口僅有一個(gè)方法GetEnumerator(),它返回一個(gè)實(shí)現(xiàn)了IEnumerable的對象。IEnumerator接口包含遍歷迭代集合所需要的代碼,它有一個(gè)屬性Current標(biāo)識當(dāng)前對象、方法MoveNext()移到下一個(gè)對象、方法Reset()重新開始。System.Collections命名空間中所有的集合類,及數(shù)組,都實(shí)現(xiàn)了IEnumerable接口,因此能被迭代。
如果你測試了由C#編譯器生成foreach的MSIL代碼,你可以看到大部分情況它僅使用IEnumerator去做迭代(特定類型,如數(shù)組和字符串,由編譯器特別處理)。下面代碼展示用IEnumerator方法實(shí)現(xiàn)上例功能的代碼:
| 1 2 3 4 5 6 | int[] values = new int[] {1, 2, 3, 4, 5}; IEnumerator e = ((IEnumerable)values).GetEnumerator(); while(e.MoveNext()) { ????Console.Write(e.Current.ToString() + " "); } |
.NET Framework使用IEnumerable和IEnumerator接口實(shí)現(xiàn)了迭代器模式。迭代器模式使我們能夠輕松地遍歷一個(gè)集合而不用了解集合內(nèi)部的工作機(jī)制。一個(gè)迭代器類,實(shí)現(xiàn)了IEnumerator接口,是一個(gè)獨(dú)立與集合的類,實(shí)現(xiàn)了IEnumerable接口。迭代器類維護(hù)遍歷的狀態(tài)(包括當(dāng)前元素是哪個(gè)和是否有更多的元素要遍歷)。這個(gè)遍歷的算法也包含在迭代器類中。這種方法可以同時(shí)有幾個(gè)迭代器,每個(gè)以不同的方式遍歷同一個(gè)集合,而不會對集合類增加任何復(fù)雜。
3、裝飾模式(Decorator Pattern)
裝飾模式:一種動(dòng)態(tài)地往一個(gè)類中添加新的行為的設(shè)計(jì)模式,通過使用修飾模式,可以在運(yùn)行時(shí)擴(kuò)充一個(gè)類的功能。原理是:增加一個(gè)修飾類包裹原來的類, 包裹的方式一般是通過在將原來的對象作為修飾類的構(gòu)造函數(shù)的參數(shù)。裝飾類實(shí)現(xiàn)新的功能,但是,在不需要用到新功能的地方,它可以直接調(diào)用原來的類中的方 法。修飾類必須和原來的類有相同的接口。UML表示如下:
圖3、裝飾模式UML表示(來源:TerryLee的.NET 設(shè)計(jì)模式(10):裝飾模式(Decorator Pattern))
任何有用的可執(zhí)行程序包括讀取輸入或?qū)戄敵?#xff0c;或者都有。盡管數(shù)據(jù)源被讀或?qū)?#xff0c;能夠把它們抽象地看成字節(jié)序列。.NET使用System.IO.Stream類去表示這個(gè)抽象。不管這些數(shù)據(jù)是包含在文本文件中字符,還是TCP/IP網(wǎng)絡(luò)流的數(shù)據(jù),或任何其他實(shí)體中,你將通過一個(gè)Stream訪問它們。因?yàn)橛糜谖募?shù)據(jù)的類(FileStream)和用于網(wǎng)絡(luò)流的類(NetWorkStream)都繼承自Stream,你可以簡單地編寫?yīng)毩⒂跀?shù)據(jù)源的代碼處理數(shù)據(jù)。下面的代碼展示從一個(gè)Stream中打印字節(jié)到控制臺:
| 1 2 3 4 5 6 7 8 | public static void PrintBytes(Stream s) { ????int b; ????while((b = fs.ReadByte()) >= 0) ????{ ????????Console.Write(b + " "); ????} } |
每次讀取單個(gè)字節(jié)通常不是最高效的訪問流的方法。例如,硬件驅(qū)動(dòng)器有能力(且優(yōu)化了)從磁盤的一大塊中讀取連續(xù)的數(shù)據(jù)塊。如果你知道你將讀取幾個(gè)字符,最好一次從磁盤中讀取一個(gè)塊然后從內(nèi)存中逐字節(jié)地使用。框架包括BufferedStream類就是做這個(gè)的。BufferedStream的構(gòu)造函數(shù)以流的類型為參數(shù),設(shè)定你想緩存訪問的類型。BufferedStream重寫了Stream的主要方法,諸如Read和Write,提供更多的功能。因?yàn)樗匀皇荢tream的子類,你可以想其他Stream一樣使用它(Note:FileStream包括他自己的緩存能力)。類似地,你可以使用System.Security.Cryptography.CryptoStream加密和解密流(Streams),除了它是一個(gè)流應(yīng)用程序不需要知道任何其他的東西。下面展示了幾種使用不同的Streams調(diào)用打印方法:
| 1 2 3 4 5 6 7 8 | MemoryStream ms = new MemoryStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); PrintBytes(ms); BufferedStream buff = new BufferedStream(ms); PrintBytes(buff); buff.Close(); FileStream fs = new FileStream("http://www.cnblogs.com/decorator.txt", FileMode.Open); PrintBytes(fs); fs.Close(); |
?
圖4、使用裝飾模式
使用組合動(dòng)態(tài)透明地附加新功能到對象的能力是裝飾模式的例子,如上圖所示。給定任何Stream的的實(shí)例,你可以通過包 裝在一個(gè)BufferedStream中添加緩沖訪問的能力,而不用改變接口數(shù)據(jù)。因?yàn)槟銉H僅是組合了對象,這可以在運(yùn)行時(shí)做,而不像繼承是編譯時(shí)決定 的。核心功能通過一個(gè)接口或者抽象類(如Stream)定義,所有裝飾都派生自它。這些裝飾自己實(shí)現(xiàn)(或重寫)接口(或抽象類)中的方法以提供額外的功 能。例如BufferedStream重寫Read方法從緩沖中讀取流,而不是直接從流中讀取。如上面的代碼所示,任何裝飾的組合,不管多復(fù)雜,可以像只 有基類一樣使用。
4、適配器模式(Adapter Pattern)
適配器模式:將一個(gè)類別的接口轉(zhuǎn)接成用戶所期待的。一個(gè)適配使得因接口不兼容而不能在一起工作的類工作在一起,做法是將類別自己的接口包裹在一個(gè)已存在的類中。有兩類適配器模式:
- 對象適配器模式——在這種適配器模式中,適配器容納一個(gè)它我包裹的類的實(shí)例。在這種情況下,適配器調(diào)用被包裹對象的物理實(shí)體。
- 類適配器模式——這種適配器模式下,適配器繼承自已實(shí)現(xiàn)的類(一般多重繼承)。
他們的UML表示分別如下:
圖5、對象適配器模式
圖6、類適配器模式(來源:TerryLee的.NET 設(shè)計(jì)模式(8):適配器模式(Adapter Pattern))
.NET Framework的優(yōu)勢之一就是向后兼容性。從基于.NET的代碼可以輕松的訪問舊的COM對象,反之亦然。為了在項(xiàng)目中使用COM組件,必須在 Visual Studio中通過添加引用對話框添加引用。在后臺Visual Studio .NET調(diào)用tlbimp.exe工具創(chuàng)建一個(gè)包含在interop程序集中的運(yùn)行時(shí)可調(diào)用包裝(Runtime Callable Wrapper,RCW)類。一旦添加了引用(且interop程序集已經(jīng)生產(chǎn)了),COM組件就可以像其它托管代碼類一樣使用。如果你在看別人寫的代碼 沒有看到引用列表(且沒有堅(jiān)持類關(guān)聯(lián)的元數(shù)據(jù)或他們的實(shí)現(xiàn)),你將不知道哪些類是用.NET的語言寫的,哪些是COM組件。
使這種神奇發(fā)生的是RCM。COM組件有不同的錯(cuò)誤處理機(jī)制及使用不同的數(shù)據(jù)類型。例如.NET Framework中的字符串使用System.String類,而COM可能使用BSTR。當(dāng)在.NET代碼中用一個(gè)字符串調(diào)用COM組件時(shí),你可以像其它托管代碼方法一樣傳遞一個(gè)System.String。在RCW內(nèi)部,在COM調(diào)用之前,這個(gè)System.String轉(zhuǎn)換成COM組件期望的格式,就像BSTR。類似地,COM組件的一個(gè)方法調(diào)用典型地返回HRESULT表示成功或失敗。當(dāng)一個(gè)COM方法調(diào)用返回表示失敗的HRESULT時(shí),RCW轉(zhuǎn)化成一個(gè)異常(默認(rèn)情況下),因此它能像其他托管代碼錯(cuò)誤一樣處理。
盡管它們的接口不一樣,但還是允許托管類和COM組件交互,RCWs是適配器模式的例子。適配器模式使一個(gè)接口適合另一個(gè),COM并不理解 System.String類,因此RCW適配器使其適應(yīng)為可以理解的。即使你不能改變舊的組件工作機(jī)制,你仍可以與它交互。這種情況經(jīng)常使用適配器。
適配器類本身包含了一個(gè)適配(Adaptee),將來自客戶端的調(diào)用轉(zhuǎn)化為合適的和順序的調(diào)用。雖然聽起來像裝飾模式,它們之間有幾個(gè)關(guān)鍵的不同。
- 裝飾模式中,組合的對象接口是相同的;適配器模式中,你可以改變整個(gè)接口。
- 適配器模式中,給他們定義了一個(gè)明確的序列,適配必須包含在適配器中;裝飾模式中,裝飾類不必知道它包裝了1個(gè)或500個(gè)其它類,因?yàn)榻涌谑窍嗤摹?
因此使用裝飾模式對應(yīng)用程序時(shí)透明的,然而使用適配器模式不是。
5、工廠模式(Factory Pattern)
工廠模式:通過調(diào)用不同的方法返回需要的類,而不是去實(shí)例化具體的類。 對實(shí)例創(chuàng)建進(jìn)行了包裝。 工廠方法是一組方法, 他們針對不同條件返回不同的類實(shí)例,這些類一般有共同的父類。它的UML表示如下:
圖7、工廠模式的UML表示(來源:TerryLee的.NET 設(shè)計(jì)模式(5):工廠方法模式(Factory Method))
許多情況下,在框架(Framework)中你可以自己不調(diào)用一個(gè)struct或class的構(gòu)造器而獲取一個(gè)新的實(shí)例。System.Convert類包含了一系列的靜態(tài)方法向這樣工作。例如,將一個(gè)整數(shù)轉(zhuǎn)換為布爾類型,你可以調(diào)用Convert.ToBollean并傳遞一個(gè)整數(shù)。如果整數(shù)是一個(gè)非零值則這個(gè)方法的返回值是一個(gè)新的Boolean并設(shè)為“true”,否則是“false”。Convert類為我們創(chuàng)建Boolean實(shí)例,并設(shè)為正確的值,其它的類型轉(zhuǎn)換也是類似。Int32和Double上的Parse方法返回這些對象的新實(shí)例并根據(jù)給定的字符串設(shè)置合適的值。
這種創(chuàng)建新對象實(shí)例的策略稱為工廠模式。不用調(diào)用對象的構(gòu)造器,你可以要求對象工廠為你創(chuàng)建一個(gè)實(shí)例。如此一來可以隱藏創(chuàng)建對象的復(fù)雜性(就像如何從一個(gè)字符串解析為一個(gè)Double值,Double.Parse)。如果你想改變創(chuàng)建對象的細(xì)節(jié),你僅需要改變工廠它本身,你不必修改每個(gè)調(diào)用構(gòu)造器的地方。
這些類型轉(zhuǎn)換方法是工廠模式的變種,因?yàn)樵趩栴}中你不必使用這個(gè)工廠創(chuàng)建對象。一個(gè)更純的工廠模式的例子是System.Net.WebRequest類,用來發(fā)出請求(request)和從Internet上的資源接受響應(yīng)(response)。FTP、HTTP和文件系統(tǒng)請求默認(rèn)頁支持。為了創(chuàng)建一個(gè)請求,調(diào)用Create方法和傳遞一個(gè)URI。Create方法決定合適的請求協(xié)議,且返回合適的WebRequest的子類:HttpWebRequest、FtpWebRequest(.NET Framework 2.0新增的)、FileWebRequest。 調(diào)用者不需要知道每個(gè)特定的協(xié)議,僅需知道如何調(diào)用工廠和使用WebRequest獲取返回值。如果URI從一個(gè)HTTP地址改變?yōu)镕TP地址,代碼根本 不用改變。這是工廠模式的另一種常用用法。父類作為一個(gè)工廠,且根據(jù)客戶端傳遞的參數(shù)返回特定派生類。就像WebRequest例子,它隱藏了選擇合適派 生類的復(fù)雜性。
6、策略模式(Strategy Pattern)
策略模式:指對象有某個(gè)行為,但是在不同的場景中,該行為有不同的實(shí)現(xiàn)算法。比如每個(gè)人都要“交個(gè)人所得稅”,但是“在美國交個(gè)人所得稅”和“在中國交個(gè)人所得稅”就有不同的算稅方法。它的UML表示如下:
圖8、策略模式的UML表示(來源:維基百科)
數(shù)組(Array)和數(shù)組列表(ArrayList)都提供通過Sort方法對集合中的對象排序的功能。實(shí)際上,ArrayList.Sort僅 對隱含的數(shù)組調(diào)用Sort。這些方法使用的是快速排序(QuickSor)算法。默認(rèn),Sort方法使用IComparable實(shí)現(xiàn)每個(gè)元素之間的比較。 有時(shí),使用不同的方法對同一列表排序非常有用。例如,字符串?dāng)?shù)組的排序可能是對大小寫敏感的或不區(qū)分大小寫的。要做到這點(diǎn),Sort函數(shù)存在重載并以一個(gè) IComparer作為參數(shù);然后IComparer.Compare用于進(jìn)行比較。重載允許類的用戶任何內(nèi)置的ICompares或自定義的,而不用做 修改甚至不用知道Array、ArrayList、或QuickSort算法的實(shí)現(xiàn)細(xì)節(jié)。
將比較算法的選擇留給類的用戶,像這種情況就是策略模式。使用策略模式允許有許多不同的可替換的算法。QuickSort本身只需要一個(gè)方法來相互比較對象。通過提供一個(gè)接口調(diào)用比較,由調(diào)用方自由選擇可替換的適合特定需要的比較算法。QuickSort的代碼可以保持不改變。
圖9、策略行為
.NET Framework 2.0的一個(gè)新的泛型集合類型——List<T>,還利用大量使用的策略模式,如上圖所示。除了更新了排序方法,find-related方法,BinarySearch,其它以根據(jù)調(diào)用者需求改變算法的參數(shù)。在FindAll<T>方法中使用一個(gè)Predicate<T>委托使調(diào)用者使用任何方法作為List<T>的過濾器,只要它接受的對象類型并返回一個(gè)布爾值。組合匿名方法,客戶可以容易地基于屬性和列表中對象的方法過濾列表,而不用引入依賴于List<T>類本身。使用策略模式使復(fù)雜的處理過程,諸如排序,容易地修改以適合特定的目的,這意味著你可以編寫和維護(hù)較少的代碼。
7、ASP.NET中的組合模式(Composite Pattern)
組合模式:將對象組合成樹形結(jié)構(gòu)以表示“部分整體”的層次結(jié)構(gòu)。組合模式使得用戶對單個(gè)對象和使用具有一致性。它的UML表示為:
圖10、組合模式的UML表示(來源:TerryLee的.NET 設(shè)計(jì)模式(11):組合模式(Composite Pattern))
ASP.NET的request/response管道(pipeline)是一個(gè)復(fù)雜的系統(tǒng)。模式用于設(shè)計(jì)管道本身和控件體系結(jié)構(gòu)有效地平衡它的可擴(kuò)展性能和簡化編程。在深入挖掘管道之前,我們檢查編程模型本身使用的模式。
當(dāng)處理對象集合時(shí),通常適合單個(gè)對象和整個(gè)集合。考慮一個(gè)ASP.NET控件,一個(gè)控件可能是一個(gè)簡單的單個(gè)元素如Literal,或可能是一個(gè)有子控件的復(fù)雜的集合如DataGrid。不管怎樣,任一這些控件上調(diào)用Render方法都將執(zhí)行相同的顯示功能。
當(dāng)集合的每個(gè)元素可能自己包含其它對象的集合時(shí),使用組合模式是合適的。組合模式是一個(gè)簡單的方法來表示樹形集合,而不用對父節(jié)點(diǎn)和葉子節(jié)點(diǎn)區(qū)別對象。
組合模式的權(quán)威例子是依賴于一個(gè)抽象基類,Component,包含添加和刪除孩子、孩子和父親之間的通用操作方法。ASP.NET正是對System.Web.UI.Control使用這種模式。Control表示Component基 類,它有一些操作,諸如處理子控件(如子空間屬性)、標(biāo)準(zhǔn)操作、屬性如Render和Visible(譯注:定義由所有 ASP.NET 服務(wù)器控件共享的屬性、方法和事件)。每個(gè)對象,不管是原始的對象(如Literal)或組合對象(如DataGrid),讀繼承自這個(gè)基類。
因?yàn)榭丶嵌鄻拥?#xff0c;有一些中間繼承類如WebControl和BaseDataList,是其它控件的基類。雖然這些類公開一些額外的屬性和方法,但他們?nèi)匀槐A艉⒆拥墓芾砉δ芎蛷腃ontrol繼承的核心操作。事實(shí)上,使用組合模式有助于隱藏他們的復(fù)雜性,如果需要的話。不管是一個(gè)Literal控件還是一個(gè)DataGrid控件,使用組合模式意味著你僅需要調(diào)用Render,事情會自行解決。
8、模板方法模式(Template Method Pattern)
模板方法模式:定義了一個(gè)算法的步驟,并允許次類別為一個(gè)或多個(gè)步驟提供其實(shí)踐方式。讓次類別在不改變算法架構(gòu)的情況下,重新定義算法中的某些步驟。模板方法多用在:
- 某些類別的算法中,實(shí)做了相同的方法,造成程式碼的重復(fù)。
- 控制次類別必須遵守的一些事項(xiàng)。
它的UML表示如下:
圖11、模板方法的UML表示(來源:維基百科)
當(dāng)ASP.NET控件的標(biāo)準(zhǔn)庫滿足不了你的需求,你有幾種選擇如何創(chuàng)建自己的。對簡單的控件僅用在一個(gè)項(xiàng)目中,用戶控件是最好的選擇。當(dāng)控件用在集合Web應(yīng)用程序中或要求更多的功能,一個(gè)自定義服務(wù)器控件也許是最好的選擇。
當(dāng)處理自定義控件,有兩個(gè)一般的類型:控件組合已存在的控件的功能(稱為組合模式)、控件有一個(gè)唯一的視覺表示。處理創(chuàng)建這些類型的控件的過程類似。對應(yīng)組合控件,創(chuàng)建一個(gè)新的類繼承自一個(gè)控件基類(像Control或WebControl),然后重寫CreateChildControls方法(譯注:該方法由 ASP.NET 頁面框架調(diào)用,以通知使用基于合成的實(shí)現(xiàn)的服務(wù)器控件創(chuàng)建它們包含的任何子控件,以便為回發(fā)或呈現(xiàn)做準(zhǔn)備)。對于其它自定義控件,你需要重寫的Render且使用HtmlTextWriter參數(shù)用于為你的控件直接輸出HTML。
不管你選擇的自定義控件的樣式,你不必寫任何處理所有控件通用的功能,如在適當(dāng)?shù)臅r(shí)間加載和保存ViewState,允許PostBack事件去處理,且確保控件的生命周期事件以正確的順序發(fā)生。控件怎么樣加載、呈現(xiàn)、卸載的主要算法包含在控件基類中。
你的控件的特定細(xì)節(jié)在控件算法的特定地方實(shí)現(xiàn)(CreateChildControls或Render方法)。這是模板方法模式的一個(gè)例子。主要算法框架定義在一個(gè)基類中,且子類可以插入他們自己的細(xì)節(jié)而不影響算法本身,如圖12所示。一個(gè)組合控件和一個(gè)自定義控件都共享同樣的一般生命周期,但是他們以完全不同的視覺表示。
圖12、模板方法模式
這個(gè)模式類似于策略模式,他們在范圍和方法上不同。
- 策略模式用于允許調(diào)用者改變整個(gè)算法,例如如何比較兩個(gè)對象,然而模板方法模式用于改變算法的步驟。正是因?yàn)檫@點(diǎn),策略模式是更粗粒度的,他們的不同客戶端實(shí)現(xiàn)可以有巨大的不同,然而模板方法模式保持框架相同。
- 另一個(gè)主要不同點(diǎn)是,策略模式使用委托,然而模板方法模式使用繼承。上面排序例子的策略模式,比較算法委托于IComparer參數(shù);但是自定義控件中,你子類的基類和重寫方法去做改變。然而,他們都使你容易地修改處理過程以適合你的特定需求。
9、ASP.NET管道中的模式(Patterns in the ASP.NET Pipeline)
當(dāng)客戶端請求一個(gè)ASPX網(wǎng)頁,在最終以HTML顯示到客戶端的瀏覽器之前,請求穿過許多步驟。首先,請求被IIS處理和路由到合適的ISAPI擴(kuò)展。ASP.NET的ISAPI擴(kuò)展(aspnet_isapi.dll)路由請求到ASP.NET工作進(jìn)程。
圖13、ASP.NET請求管道
在這一點(diǎn),請求開始與處理請求的類進(jìn)行交互。請求被傳遞到一個(gè)HttpApplication。一般地,這個(gè)類在Global.asax的后置代碼文件中創(chuàng)建。HttpApplication然后傳遞請求給一些HTTP模塊。這些類實(shí)現(xiàn)了IHttpModule接 口且在傳遞給下一模塊之前可以修改請求(甚至可以停止請求)。ASP.NET提供了一些標(biāo)準(zhǔn)的模塊提供常用的功能,包括 FormsAuthenticationModule、PassportAuthenticationModule、 WindowsAuthentication、SessionStateModule等等。
最終,請求結(jié)束在一個(gè)IHttpHandler,最常用的是System.Web.UI.Page的IHttpHandler.ProcessRequest方法。Page產(chǎn)生合適的事件(例如Init、Load、Render),處理ViewState,提供ASP.NET的編程模型。圖13展示了一個(gè)整理輪廓。
在這個(gè)處理過程中采用了幾個(gè)模式,更深入的可以參考Martin Fowler's Patterns of Enterprise Application Architecture(Addison-Wesley, 2002)。下面介紹其中用到的幾個(gè)模式。
9.1、截取過濾器模式(Intercepting Filter Pattern)
一旦一個(gè)請求到達(dá)HttpApplication,它將傳遞到一些IHttpModules。每個(gè)模塊是獨(dú)立的且僅有數(shù)量有限的控制加在調(diào)用順序 上。HttpApplication類公開一系列的事件,在請求的處理過程中產(chǎn)生這些事件包括BeginRequest、 AuthenticateRequest、AuthorizeRequest、EndRequest。當(dāng)HttpApplication加載一個(gè)模塊,它 調(diào)用IHttpModule接口的Init方法,允許模塊注冊關(guān)心它的一些事件。作為一個(gè)給定的請求被處理,事件以合適的順序產(chǎn)生且所有注冊的模塊可以與 請求交互。因此,模塊可以控制在它被調(diào)用的階段,但不是這一階段的確切順序。
這些模塊是截取過濾模式的例子,這個(gè)模式表示一個(gè)過濾器鏈,每個(gè)過濾器依次有機(jī)會修改請求(或消息)傳遞他們。圖14展示了這個(gè)過程的一個(gè)簡單的流程圖。這個(gè)模式的關(guān)鍵思想是過濾器是獨(dú)立的,過濾器可以修改傳遞到它的請求。
圖14、請求流程
有幾種不同的截取過濾器模式變種的實(shí)現(xiàn),一種是ASP.NET的基于事件的模型。一個(gè)簡單的變種包括維護(hù)一個(gè)過濾器列表 并對它迭代,依次對它們每個(gè)調(diào)用一個(gè)方法。這是Web Service Enhancements(WSE)為ASP.ENT Service如何使用這個(gè)模式。每個(gè)過濾器擴(kuò)展自SoapInputFilter(為請求消息)或SoapOutputFilter(為響應(yīng)),重寫ProcessMessage方法執(zhí)行過濾器的工作。
另外一種選擇是通過裝飾模式實(shí)現(xiàn)截取過濾器。每個(gè)過濾器將包裝它的后繼,執(zhí)行預(yù)處理,調(diào)用它的后繼,然后執(zhí)行后處理。通過遞歸組合構(gòu)建鏈,從后往前。這個(gè)模式用于實(shí)現(xiàn).NET Remoting管道接收器。
無論其實(shí)現(xiàn),結(jié)果是獨(dú)立過濾器的動(dòng)態(tài)可配置鏈。因?yàn)樗麄兪仟?dú)立的,這些過濾器可以容易地在其他應(yīng)用程序中重排序和重用。通用任務(wù)如身份鑒定或日志可以封裝在一個(gè)過濾器中且反復(fù)使用。這些任務(wù)可以在請求到達(dá)HttpHandler之前被過濾器鏈處理,保持處理代碼整潔。
9.2、頁面控制器模式(Page Controller Pattern)
System.Web.UI.Page實(shí) 現(xiàn)ASP.NET編程模型的核心部分。每當(dāng)你要添加一個(gè)邏輯頁面到一個(gè)Web應(yīng)用程序,你可以創(chuàng)建一個(gè)Web Form(通過一個(gè)ASPX文件和它的后置代碼文件表示)。然后你可以編寫代碼處理新頁面的具體需求,無論是通過處理頁面級事件,顯示一個(gè)組控件,或加載 和操作數(shù)據(jù)。應(yīng)用程序中的每個(gè)邏輯頁面有一個(gè)相應(yīng)的Web Form控制它的行為和調(diào)整它的顯示。
每個(gè)邏輯頁有一個(gè)控制器是頁面控制器模式的一個(gè)例子,這種想法是ASP.NET的基礎(chǔ)。當(dāng)一個(gè)邏輯頁面通過一個(gè)URI被請求時(shí),ASP.NET運(yùn)行 時(shí)解析地址到相應(yīng)的頁面子類且使用該類去處理請求。所有的關(guān)于頁面是什么樣子的細(xì)節(jié)、哪些用戶輸入可以處理和如何響應(yīng)輸入都包含在一個(gè)地方。當(dāng)應(yīng)用程序中 的邏輯頁要求改變時(shí),其它頁面不受影響。這是一種非常普遍的抽象,以致我們甚至不考慮它。
這樣的缺點(diǎn)之一是常與純粹的頁面控制器實(shí)現(xiàn)關(guān)聯(lián),共同的代碼必須在每個(gè)頁面重復(fù)。ASP.NET通過包括管道實(shí)現(xiàn)的其它模式來避免這個(gè)缺點(diǎn),并提供System.Web.UI.Page作為所有頁面控制器的共同基類。交叉剪切(Cross-cutting)關(guān)心諸如身份認(rèn)證和會話狀態(tài)由HttpModule截取過濾器處理,且產(chǎn)生頁面生命周期事件,及其他由基類處理的活動(dòng)。
9.3、ASP.NET中的其它web顯示模式(Other Web Presentation Patterns in ASP.NET)
除了截取過濾器和頁面控制器,ASP.NET使用了其它幾種Web顯示模式的變種。當(dāng)ASP.NET決定哪個(gè)HttpHandler去傳遞請求,它使用類似于前端控制器(Front Controller)的模式。前端控制器的特點(diǎn)是有一個(gè)處理器處理所有的請求(如System.Web.UI.Page)。然而一旦請求達(dá)到頁面類,頁面控制器模式結(jié)束。
在頁面控制器的ASP.NET實(shí)現(xiàn)中,有一些Model View Controller模式的元素。Model View Controller從view(顯示信息)分離了model(業(yè)務(wù)對象,數(shù)據(jù)和流程)。控制器響應(yīng)用戶輸入和更新model和view。大約來說,一個(gè) ASPX頁面便是View,然而它的后置代碼文件表示Model-Controller的混合。如果你從后置代碼文件拉取出所有的業(yè)務(wù)和數(shù)據(jù)相關(guān)且使它僅 僅是事件處理代碼,這將后置代碼變成一個(gè)純粹的控制器,而其它的包含業(yè)務(wù)邏輯的類將是Model。
因此那些從經(jīng)典模式中分離的,不在這里討論了,Martin Fowler的書和Microsoft Patterns網(wǎng)站中查看更多的討論。
10、總結(jié)
現(xiàn)在我們已經(jīng)研究了在.NET Framework和BCL中使用的通常模式,可以容易地識別每天在編寫代碼時(shí)使用了相同的模式。希望通過突出顯示通用類和功能隱含使用的設(shè)計(jì)模式,使我 們更好地理解設(shè)計(jì)模式和它們提供的好處。試想如果UI編程而不用觀察者模式或集合不使用迭代器模式,表明他們是framework不可或缺的。"Once you understand what each does, it becomes another valuable tool to add to your toolbox."
總結(jié)
以上是生活随笔為你收集整理的.NET(C#) Internals: .NET Framework中已使用的设计模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: unity3D 5中如何修改及显示Tex
- 下一篇: 4月全球域名商域名解析量23强:西数解析