1. 程式人生 > >.NET(C#) Internals: .NET Framework中已使用的設計模式

.NET(C#) Internals: .NET Framework中已使用的設計模式

——適合有一定設計模式基礎和.NET基礎的人閱讀。

寫在前面

“設計模式”我一向是敬而遠之的態度,不會去寫這方面的文章,原因有二:第一,要想寫好設計模式的文章太難,需要筆者豐富的經驗;第二,沒有深厚的功底寫出的設計模式文章容易誤導他人。自認沒有深厚的功底,但我不會為了設計模式而設計模式。我想大部分人對設計模式的理解是不夠深刻的,不然應用自如,特別是初學者!所有研究高質量的原始碼或框架是我們學習實踐設計模式的好途徑之一。

而我之所以寫這篇文章,主要是因為它從.NET Framework入手介紹已經使用的設計模式,作為一個.NET開發人員應該再熟悉不過了,能夠有比較深刻的認識和理解。本文從.NET Framework中入手,發掘在.NET Framework中如何使用設計模式的。從中我們知道我們平時使用.NET時,我們使用了那些模式及學習使用設計模式。本文意譯自

Discover the Design Patterns You're Already Using in the .NET Framework及加入了相關設計模式的UML表示和主要介紹。

主要內容如下:

  • .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)
  • 總結

1、觀察者模式(Observer Pattern)

觀察者模式:在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常通過呼叫各觀察者所提供的方法來實現。它的UML表示如下:

Observer-pattern-class-diagram圖1、觀察者模式的UML表示 (來源:維基百科

好的面向物件設計都強調封裝(encapsulation)和鬆耦合(loose coupling)。換句話說,類應該保持內部細節私有並且最小化類之間嚴格的依賴關係。大部分應用程式,類並不是獨立工作的,而是與其他類互動的。類互動的一個通常例子是:一個類應該(觀察者,Observer)被通知,當被觀察者(Subject)的某些東西改變了。例如,當單擊一個按鈕後可能某些Windows Forms的控制元件需要更新他們的顯示。一個簡單的解決方案是,當狀態改變時讓被觀察者呼叫觀察者特定的方法。但是,這回引入一連串的問題。因為被觀察者需要知道呼叫哪個方法,這樣就與特定觀察者產生了緊耦合(tight coupling)。而且,如果當需要新增多個觀察者時,不得不繼續新增每個觀察者方法呼叫的程式碼。如果觀察者的數量動態地改變,這將變得更復雜。這將很難維護!

應用觀察者模式能有效地解決這個問題。可以從觀察者解耦被觀察者,因此在設計時和執行時觀察者可以容易地新增和移除。被觀察者維護者一個對它感興趣的觀察者列表,每次被觀察者的狀態改變時,它對每個觀察者呼叫Notify方法。下面這段程式碼展示了一個實現示例:

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();
}

所有的觀察者類實現ICanonicalObserver介面,所有的被觀察者必須繼承自CanonicalSubjectBase。如果一個新的觀察者想監視被觀察者,Add方法可以輕鬆的處理而不必改變被觀察者類的程式碼。注意:每個被觀察者僅僅直接依賴於ICanonicalObserver介面,而不是特定的觀察者。

然而使用GOF的觀察者模式解決這些問題仍有一些障礙,因為被觀察者必須繼承一個特定的基類且觀察者必須實現一個特定介面。考慮回Windows Forms按鈕的例子,.NET Framework引入了委託事件來解決這些問題。如果你已經編寫過ASP.NET或Windows Forms程式,你可能就是有了事件和事件處理器。事件作為被觀察者,然而委託作為觀察者。下面程式碼展示了使用事件的觀察者模式:

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控制元件公開一個Click事件,當button被點選時產生。任何設計為響應這個事件的類僅需要用這個事件註冊一個委託。Button類不依賴與任何潛在的觀察者,並且每個觀察者僅需要知道這個事件的委託的正確型別(這裡是EventHandler)。因為EventHandler是一個委託型別而不是一個介面,每個觀察者不需要實現一個額外的介面。假定它已經包含一個與簽名相容的方法,只需要用被觀察者的事件註冊方法。通過使用委託和事件,觀察者模式使被觀察者與觀察者們之間解耦了。

2、迭代器模式(Iterator Pattern)

迭代器模式:它可以讓使用者通過特定的介面巡訪容器中的每一個元素而不用瞭解底層的實作。它的UML表示如下:

許多程式設計任務包括操作物件的集合。不管這些集合是簡單的列表還是更復雜的,如二叉樹,經常需要訪問集合中的每個物件。事實上,根據集合可能有幾種不同的訪問每個物件的方法,諸如從前向後、從後向前、前序或後序。為了保持集合簡單,遍歷程式碼通常放在自己單獨的類中。

儲存一個物件列表的常用方法之一就是用陣列。陣列型別在Visual Basic.NET和C#中都是內建型別,他們都有一個迴圈結構用於在陣列上迭代:foreach(C#)和For Each(Visual Basic.NET)。下面是一個在陣列上進行迭代的簡單例子:

int[] values = new int[] {1, 2, 3, 4, 5};

foreach(int i in values)
{
    Console.Write(i.ToString() + " ");
}

這些語句在後臺對陣列使用了迭代器。我們需要知道的就是它保證了迴圈保證了對陣列中的每個元素進行一次遍歷。

為了使這些語句起作用,foreach表示式中涉及的物件必須實現了IEnumerable介面。任何實現了IEnumerable介面的物件集合都可以被遍歷(列舉)。這個介面僅有一個方法,它返回一個實現了IEnumerable的物件。IEnumerator介面包含遍歷迭代集合所需要的程式碼,它有一個屬性Current標識當前物件、方法MoveNext()移到下一個物件、方法Reset()重新開始。System.Collections名稱空間中所有的集合類,及陣列,都實現了IEnumerable介面,因此能被迭代。

如果你測試了由C#編譯器生成foreach的MSIL程式碼,你可以看到大部分情況它僅使用IEnumerator去做迭代(特定型別,如陣列和字串,由編譯器特別處理)。下面程式碼展示用IEnumerator方法實現上例功能的程式碼:

int[] values = new int[] {1, 2, 3, 4, 5};
IEnumerator e = ((IEnumerable)values).GetEnumerator();
while(e.MoveNext())
{
    Console.Write(e.Current.ToString() + " ");
}

.NET Framework使用IEnumerableIEnumerator介面實現了迭代器模式。迭代器模式使我們能夠輕鬆地遍歷一個集合而不用瞭解集合內部的工作機制。一個迭代器類,實現了IEnumerator介面,是一個獨立與集合的類,實現了IEnumerable介面。迭代器類維護遍歷的狀態(包括當前元素是哪個和是否有更多的元素要遍歷)。這個遍歷的演算法也包含在迭代器類中。這種方法可以同時有幾個迭代器,每個以不同的方式遍歷同一個集合,而不會對集合類增加任何複雜。

3、裝飾模式(Decorator Pattern)

裝飾模式:一種動態地往一個類中新增新的行為的設計模式,通過使用修飾模式,可以在執行時擴充一個類的功能。原理是:增加一個修飾類包裹原來的類,包裹的方式一般是通過在將原來的物件作為修飾類的建構函式的引數。裝飾類實現新的功能,但是,在不需要用到新功能的地方,它可以直接呼叫原來的類中的方法。修飾類必須和原來的類有相同的介面。UML表示如下:

任何有用的可執行程式包括讀取輸入或寫輸出,或者都有。儘管資料來源被讀或寫,能夠把它們抽象地看成位元組序列。.NET使用System.IO.Stream類去表示這個抽象。不管這些資料是包含在文字檔案中字元,還是TCP/IP網路流的資料,或任何其他實體中,你將通過一個Stream訪問它們。因為用於檔案資料的類(FileStream)和用於網路流的類(NetWorkStream)都繼承自Stream,你可以簡單地編寫獨立於資料來源的程式碼處理資料。下面的程式碼展示從一個Stream中列印位元組到控制檯:

public static void PrintBytes(Stream s)
{
    int b;
    while((b = fs.ReadByte()) >= 0)
    {
        Console.Write(b + " ");
    }
}

每次讀取單個位元組通常不是最高效的訪問流的方法。例如,硬體驅動器有能力(且優化了)從磁碟的一大塊中讀取連續的資料塊。如果你知道你將讀取幾個字元,最好一次從磁碟中讀取一個塊然後從記憶體中逐位元組地使用。框架包括BufferedStream類就是做這個的。BufferedStream的建構函式以流的型別為引數,設定你想快取訪問的型別。BufferedStream重寫了Stream的主要方法,諸如ReadWrite,提供更多的功能。因為它仍然是Stream的子類,你可以想其他Stream一樣使用它(Note:FileStream包括他自己的快取能力)。類似地,你可以使用System.Security.Cryptography.CryptoStream加密和解密流(Streams),除了它是一個流應用程式不需要知道任何其他的東西。下面展示了幾種使用不同的Streams呼叫列印方法:

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();

cc188707.fig04(en-us)

圖4、使用裝飾模式

使用組合動態透明地附加新功能到物件的能力是裝飾模式的例子,如上圖所示。給定任何Stream的的例項,你可以通過包裝在一個BufferedStream中新增緩衝訪問的能力,而不用改變介面資料。因為你僅僅是組合了物件,這可以在執行時做,而不像繼承是編譯時決定的。核心功能通過一個介面或者抽象類(如Stream)定義,所有裝飾都派生自它。這些裝飾自己實現(或重寫)介面(或抽象類)中的方法以提供額外的功能。例如BufferedStream重寫Read方法從緩衝中讀取流,而不是直接從流中讀取。如上面的程式碼所示,任何裝飾的組合,不管多複雜,可以像只有基類一樣使用。

4、介面卡模式(Adapter Pattern)

介面卡模式:將一個類別的介面轉接成使用者所期待的。一個適配使得因介面不相容而不能在一起工作的類工作在一起,做法是將類別自己的介面包裹在一個已存在的類中。有兩類介面卡模式:

  • 物件介面卡模式——在這種介面卡模式中,介面卡容納一個它我包裹的類的例項。在這種情況下,介面卡呼叫被包裹物件的物理實體。
  • 類介面卡模式——這種介面卡模式下,介面卡繼承自已實現的類(一般多重繼承)。

他們的UML表示分別如下:

物件介面卡模式

圖5、物件介面卡模式

類介面卡模式

.NET Framework的優勢之一就是向後相容性。從基於.NET的程式碼可以輕鬆的訪問舊的COM物件,反之亦然。為了在專案中使用COM元件,必須在Visual Studio中通過新增引用對話方塊新增引用。在後臺Visual Studio .NET呼叫tlbimp.exe工具建立一個包含在interop程式集中的執行時可呼叫包裝(Runtime Callable Wrapper,RCW)類。一旦添加了引用(且interop程式集已經生產了),COM元件就可以像其它託管程式碼類一樣使用。如果你在看別人寫的程式碼沒有看到引用列表(且沒有堅持類關聯的元資料或他們的實現),你將不知道哪些類是用.NET的語言寫的,哪些是COM元件。

使這種神奇發生的是RCM。COM元件有不同的錯誤處理機制及使用不同的資料型別。例如.NET Framework中的字串使用System.String類,而COM可能使用BSTR。當在.NET程式碼中用一個字串呼叫COM元件時,你可以像其它託管程式碼方法一樣傳遞一個System.String。在RCW內部,在COM呼叫之前,這個System.String轉換成COM元件期望的格式,就像BSTR。類似地,COM元件的一個方法呼叫典型地返回HRESULT表示成功或失敗。當一個COM方法呼叫返回表示失敗的HRESULT時,RCW轉化成一個異常(預設情況下),因此它能像其他託管程式碼錯誤一樣處理。

儘管它們的介面不一樣,但還是允許託管類和COM元件互動,RCWs是介面卡模式的例子。介面卡模式使一個介面適合另一個,COM並不理解System.String類,因此RCW介面卡使其適應為可以理解的。即使你不能改變舊的元件工作機制,你仍可以與它互動。這種情況經常使用介面卡。

介面卡類本身包含了一個適配(Adaptee),將來自客戶端的呼叫轉化為合適的和順序的呼叫。雖然聽起來像裝飾模式,它們之間有幾個關鍵的不同。

  • 裝飾模式中,組合的物件介面是相同的;介面卡模式中,你可以改變整個介面。
  • 介面卡模式中,給他們定義了一個明確的序列,適配必須包含在介面卡中;裝飾模式中,裝飾類不必知道它包裝了1個或500個其它類,因為介面是相同的。

因此使用裝飾模式對應用程式時透明的,然而使用介面卡模式不是。

5、工廠模式(Factory Pattern)

工廠模式:通過呼叫不同的方法返回需要的類,而不是去例項化具體的類。 對例項建立進行了包裝。 工廠方法是一組方法, 他們針對不同條件返回不同的類例項,這些類一般有共同的父類。它的UML表示如下:

許多情況下,在框架(Framework)中你可以自己不呼叫一個struct或class的構造器而獲取一個新的例項。System.Convert類包含了一系列的靜態方法向這樣工作。例如,將一個整數轉換為布林型別,你可以呼叫Convert.ToBollean並傳遞一個整數。如果整數是一個非零值則這個方法的返回值是一個新的Boolean並設為“true”,否則是“false”。Convert類為我們建立Boolean例項,並設為正確的值,其它的型別轉換也是類似。Int32和Double上的Parse方法返回這些物件的新例項並根據給定的字串設定合適的值。

這種建立新物件例項的策略稱為工廠模式。不用呼叫物件的構造器,你可以要求物件工廠為你建立一個例項。如此一來可以隱藏建立物件的複雜性(就像如何從一個字串解析為一個Double值,Double.Parse)。如果你想改變建立物件的細節,你僅需要改變工廠它本身,你不必修改每個呼叫構造器的地方。

這些型別轉換方法是工廠模式的變種,因為在問題中你不必使用這個工廠建立物件。一個更純的工廠模式的例子是System.Net.WebRequest類,用來發出請求(request)和從Internet上的資源接受響應(response)。FTP、HTTP和檔案系統請求預設頁支援。為了建立一個請求,呼叫Create方法和傳遞一個URI。Create方法決定合適的請求協議,且返回合適的WebRequest的子類:HttpWebRequestFtpWebRequest(.NET Framework 2.0新增的)、FileWebRequest。呼叫者不需要知道每個特定的協議,僅需知道如何呼叫工廠和使用WebRequest獲取返回值。如果URI從一個HTTP地址改變為FTP地址,程式碼根本不用改變。這是工廠模式的另一種常用用法。父類作為一個工廠,且根據客戶端傳遞的引數返回特定派生類。就像WebRequest例子,它隱藏了選擇合適派生類的複雜性。

6、策略模式(Strategy Pattern)

策略模式:指物件有某個行為,但是在不同的場景中,該行為有不同的實現演算法。比如每個人都要“交個人所得稅”,但是“在美國交個人所得稅”和“在中國交個人所得稅”就有不同的算稅方法。它的UML表示如下:

500px-Strategy_Pattern_Diagram_ZP.svg圖8、策略模式的UML表示(來源:維基百科)

陣列(Array)和陣列列表(ArrayList)都提供通過Sort方法對集合中的物件排序的功能。實際上,ArrayList.Sort僅對隱含的陣列呼叫Sort。這些方法使用的是快速排序(QuickSor)演算法。預設,Sort方法使用IComparable實現每個元素之間的比較。有時,使用不同的方法對同一列表排序非常有用。例如,字串陣列的排序可能是對大小寫敏感的或不區分大小寫的。要做到這點,Sort函式存在過載並以一個IComparer作為引數;然後IComparer.Compare用於進行比較。過載允許類的使用者任何內建的ICompares或自定義的,而不用做修改甚至不用知道Array、ArrayList、或QuickSort演算法的實現細節。

將比較演算法的選擇留給類的使用者,像這種情況就是策略模式。使用策略模式允許有許多不同的可替換的演算法。QuickSort本身只需要一個方法來相互比較物件。通過提供一個介面呼叫比較,由呼叫方自由選擇可替換的適合特定需要的比較演算法。QuickSort的程式碼可以保持不改變。

cc188707.fig05(en-us)圖9、策略行為

.NET Framework 2.0的一個新的泛型集合型別——List<T>,還利用大量使用的策略模式,如上圖所示。除了更新了排序方法,find-related方法,BinarySearch,其它以根據呼叫者需求改變演算法的引數。在FindAll<T>方法中使用一個Predicate<T>委託使呼叫者使用任何方法作為List<T>的過濾器,只要它接受的物件型別並返回一個布林值。組合匿名方法,客戶可以容易地基於屬性和列表中物件的方法過濾列表,而不用引入依賴於List<T>類本身。使用策略模式使複雜的處理過程,諸如排序,容易地修改以適合特定的目的,這意味著你可以編寫和維護較少的程式碼。

7、ASP.NET中的組合模式(Composite Pattern)

組合模式:將物件組合成樹形結構以表示“部分整體”的層次結構。組合模式使得使用者對單個物件和使用具有一致性。它的UML表示為:

ASP.NET的request/response管道(pipeline)是一個複雜的系統。模式用於設計管道本身和控制元件體系結構有效地平衡它的可擴充套件效能和簡化程式設計。在深入挖掘管道之前,我們檢查程式設計模型本身使用的模式。

當處理物件集合時,通常適合單個物件和整個集合。考慮一個ASP.NET控制元件,一個控制元件可能是一個簡單的單個元素如Literal,或可能是一個有子控制元件的複雜的集合如DataGrid。不管怎樣,任一這些控制元件上呼叫Render方法都將執行相同的顯示功能。

當集合的每個元素可能自己包含其它物件的集合時,使用組合模式是合適的。組合模式是一個簡單的方法來表示樹形集合,而不用對父節點和葉子節點區別物件。

組合模式的權威例子是依賴於一個抽象基類,Component,包含新增和刪除孩子、孩子和父親之間的通用操作方法。ASP.NET正是對System.Web.UI.Control使用這種模式。Control表示Component基類,它有一些操作,諸如處理子控制元件(如子空間屬性)、標準操作、屬性如Render和Visible(譯註:定義由所有 ASP.NET 伺服器控制元件共享的屬性、方法和事件)。每個物件,不管是原始的物件(如Literal)或組合物件(如DataGrid),讀繼承自這個基類。

因為控制元件是多樣的,有一些中間繼承類如WebControlBaseDataList,是其它控制元件的基類。雖然這些類公開一些額外的屬性和方法,但他們仍然保留孩子的管理功能和從Control繼承的核心操作。事實上,使用組合模式有助於隱藏他們的複雜性,如果需要的話。不管是一個Literal控制元件還是一個DataGrid控制元件,使用組合模式意味著你僅需要呼叫Render,事情會自行解決。

8、模板方法模式(Template Method Pattern)

模板方法模式:定義了一個演算法的步驟,並允許次類別為一個或多個步驟提供其實踐方式。讓次類別在不改變演算法架構的情況下,重新定義演算法中的某些步驟。模板方法多用在:

  • 某些類別的演算法中,實做了相同的方法,造成程式碼的重複。 
  • 控制次類別必須遵守的一些事項。

它的UML表示如下:

Template_Method_UML.svg圖11、模板方法的UML表示(來源:維基百科

當ASP.NET控制元件的標準庫滿足不了你的需求,你有幾種選擇如何建立自己的。對簡單的控制元件僅用在一個專案中,使用者控制元件是最好的選擇。當控制元件用在集合Web應用程式中或要求更多的功能,一個自定義伺服器控制元件也許是最好的選擇。

當處理自定義控制元件,有兩個一般的型別:控制元件組合已存在的控制元件的功能(稱為組合模式)、控制元件有一個唯一的視覺表示。處理建立這些型別的控制元件的過程類似。對應組合控制元件,建立一個新的類繼承自一個控制元件基類(像Control或WebControl),然後重寫CreateChildControls方法(譯註:該方法由 ASP.NET 頁面框架呼叫,以通知使用基於合成的實現的伺服器控制元件建立它們包含的任何子控制元件,以便為回發或呈現做準備)。對於其它自定義控制元件,你需要重寫的Render且使用HtmlTextWriter引數用於為你的控制元件直接輸出HTML。

不管你選擇的自定義控制元件的樣式,你不必寫任何處理所有控制元件通用的功能,如在適當的時間載入和儲存ViewState,允許PostBack事件去處理,且確保控制元件的生命週期事件以正確的順序發生。控制元件怎麼樣載入、呈現、解除安裝的主要演算法包含在控制元件基類中

你的控制元件的特定細節在控制元件演算法的特定地方實現(CreateChildControlsRender方法)。這是模板方法模式的一個例子。主要演算法框架定義在一個基類中,且子類可以插入他們自己的細節而不影響演算法本身,如圖12所示。一個組合控制元件和一個自定義控制元件都共享同樣的一般生命週期,但是他們以完全不同的視覺表示。

cc188707.fig06(en-us)圖12、模板方法模式

這個模式類似於策略模式,他們在範圍和方法上不同。

  • 策略模式用於允許呼叫者改變整個演算法,例如如何比較兩個物件,然而模板方法模式用於改變演算法的步驟。正是因為這點,策略模式是更粗粒度的,他們的不同客戶端實現可以有巨大的不同,然而模板方法模式保持框架相同。
  • 另一個主要不同點是,策略模式使用委託,然而模板方法模式使用繼承。上面排序例子的策略模式,比較演算法委託於IComparer引數;但是自定義控制元件中,你子類的基類和重寫方法去做改變。然而,他們都使你容易地修改處理過程以適合你的特定需求。

9、ASP.NET管道中的模式(Patterns in the ASP.NET Pipeline)

當客戶端請求一個ASPX網頁,在最終以HTML顯示到客戶端的瀏覽器之前,請求穿過許多步驟。首先,請求被IIS處理和路由到合適的ISAPI擴充套件。ASP.NET的ISAPI擴充套件(aspnet_isapi.dll)路由請求到ASP.NET工作程序。

cc188707.fig07(en-us)圖13、ASP.NET請求管道

在這一點,請求開始與處理請求的類進行互動。請求被傳遞到一個HttpApplication。一般地,這個類在Global.asax的後置程式碼檔案中建立。HttpApplication然後傳遞請求給一些HTTP模組。這些類實現了IHttpModule介面且在傳遞給下一模組之前可以修改請求(甚至可以停止請求)。ASP.NET提供了一些標準的模組提供常用的功能,包括FormsAuthenticationModule、PassportAuthenticationModule、WindowsAuthentication、SessionStateModule等等。

最終,請求結束在一個IHttpHandler,最常用的是System.Web.UI.Page的IHttpHandler.ProcessRequest方法。Page產生合適的事件(例如Init、Load、Render),處理ViewState,提供ASP.NET的程式設計模型。圖13展示了一個整理輪廓。

在這個處理過程中採用了幾個模式,更深入的可以參考Martin Fowler's Patterns of Enterprise Application Architecture(Addison-Wesley, 2002)。下面介紹其中用到的幾個模式。

9.1、擷取過濾器模式(Intercepting Filter Pattern)

一旦一個請求到達HttpApplication,它將傳遞到一些IHttpModules。每個模組是獨立的且僅有數量有限的控制加在呼叫順序上。HttpApplication類公開一系列的事件,在請求的處理過程中產生這些事件包括BeginRequest、AuthenticateRequest、AuthorizeRequest、EndRequest。當HttpApplication載入一個模組,它呼叫IHttpModule介面的Init方法,允許模組註冊關心它的一些事件。作為一個給定的請求被處理,事件以合適的順序產生且所有註冊的模組可以與請求互動。因此,模組可以控制在它被呼叫的階段,但不是這一階段的確切順序。

這些模組是擷取過濾模式的例子,這個模式表示一個過濾器鏈,每個過濾器依次有機會修改請求(或訊息)傳遞他們。圖14展示了這個過程的一個簡單的流程圖。這個模式的關鍵思想是過濾器是獨立的,過濾器可以修改傳遞到它的請求。

cc188707.fig08(en-us)圖14、請求流程

有幾種不同的擷取過濾器模式變種的實現,一種是ASP.NET的基於事件的模型。一個簡單的變種包括維護一個過濾器列表並對它迭代,依次對它們每個呼叫一個方法。這是Web Service Enhancements(WSE)為ASP.ENT Service如何使用這個模式。每個過濾器擴充套件自SoapInputFilter(為請求訊息)或SoapOutputFilter(為響應),重寫ProcessMessage方法執行過濾器的工作。

另外一種選擇是通過裝飾模式實現擷取過濾器。每個過濾器將包裝它的後繼,執行預處理,呼叫它的後繼,然後執行後處理。通過遞迴組合構建鏈,從後往前。這個模式用於實現.NET Remoting管道接收器。

無論其實現,結果是獨立過濾器的動態可配置鏈。因為他們是獨立的,這些過濾器可以容易地在其他應用程式中重排序和重用。通用任務如身份鑑定或日誌可以封裝在一個過濾器中且反覆使用。這些任務可以在請求到達HttpHandler之前被過濾器鏈處理,保持處理程式碼整潔。

9.2、頁面控制器模式(Page Controller Pattern)

System.Web.UI.Page實現ASP.NET程式設計模型的核心部分。每當你要新增一個邏輯頁面到一個Web應用程式,你可以建立一個Web Form(通過一個ASPX檔案和它的後置程式碼檔案表示)。然後你可以編寫程式碼處理新頁面的具體需求,無論是通過處理頁面級事件,顯示一個組控制元件,或載入和操作資料。應用程式中的每個邏輯頁面有一個相應的Web Form控制它的行為和調整它的顯示。

每個邏輯頁有一個控制器是頁面控制器模式的一個例子,這種想法是ASP.NET的基礎。當一個邏輯頁面通過一個URI被請求時,ASP.NET執行時解析地址到相應的頁面子類且使用該類去處理請求。所有的關於頁面是什麼樣子的細節、哪些使用者輸入可以處理和如何響應輸入都包含在一個地方。當應用程式中的邏輯頁要求改變時,其它頁面不受影響。這是一種非常普遍的抽象,以致我們甚至不考慮它。

這樣的缺點之一是常與純粹的頁面控制器實現關聯,共同的程式碼必須在每個頁面重複。ASP.NET通過包括管道實現的其它模式來避免這個缺點,並提供System.Web.UI.Page作為所有頁面控制器的共同基類。交叉剪下(Cross-cutting)關心諸如身份認證和會話狀態由HttpModule擷取過濾器處理,且產生頁面生命週期事件,及其他由基類處理的活動。

9.3、ASP.NET中的其它web顯示模式(Other Web Presentation Patterns in ASP.NET)

除了擷取過濾器和頁面控制器,ASP.NET使用了其它幾種Web顯示模式的變種。當ASP.NET決定哪個HttpHandler去傳遞請求,它使用類似於前端控制器(Front Controller)的模式。前端控制器的特點是有一個處理器處理所有的請求(如System.Web.UI.Page)。然而一旦請求達到頁面類,頁面控制器模式結束。

在頁面控制器的ASP.NET實現中,有一些Model View Controller模式的元素。Model View Controller從view(顯示資訊)分離了model(業務物件,資料和流程)。控制器響應使用者輸入和更新model和view。大約來說,一個ASPX頁面便是View,然而它的後置程式碼檔案表示Model-Controller的混合。如果你從後置程式碼檔案拉取出所有的業務和資料相關且使它僅僅是事件處理程式碼,這將後置程式碼變成一個純粹的控制器,而其它的包含業務邏輯的類將是Model。

因此那些從經典模式中分離的,不在這裡討論了,Martin Fowler的書和Microsoft Patterns網站中檢視更多的討論。

10、總結

現在我們已經研究了在.NET Framework和BCL中使用的通常模式,可以容易地識別每天在編寫程式碼時使用了相同的模式。希望通過突出顯示通用類和功能隱含使用的設計模式,使我們更好地理解設計模式和它們提供的好處。試想如果UI程式設計而不用觀察者模式或集合不使用迭代器模式,表明他們是framework不可或缺的。"Once you understand what each does, it becomes another valuable tool to add to your toolbox."