Henry 譯 (2002.10.14) [Henry注: 本文并不復雜,可以為.net" name="description" />
Henry手記—使用Template Method設計模式的
.NET事件處理機制(一)
MILY: 宋體; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'"> Henry 譯(2002.10.14)
[Henry注: 本文并不復雜,可以為.net事件處理的中級讀物。本文雖為翻譯,但并不是完全精確之譯文,加入了Henry自己的看法,如有謬誤,責任在Henry矣]
1. 引言
Microsoft .NET事件處理,和標準的面向對象架構一樣,使用的是著名的Observer設計模式(請參看書籍《設計模式(Design Patterns)》, Gamma et al., Addison-Wesley, 1995, pp325-330)。本文描述了如何利用Template Method(Henry注:模板方法,總覺得不翻出來更親切)設計模式去增強.NET的事件處理機制。討論與代碼片斷是基于C#的,但結論示例是使用了C#和Visual Basic.NET分別實現的。
本文討論的思想是出自于Tomas Restrepo在2002年3月出版的《Visual Systems Journal》,該文是基于Microsoft MSDN Library的.NET專題提供的標準事件處理示例:《 Design Guidelines for Class Library Developers》 (詳見文中的 "事件使用向導"一節)。
對于事件處理,最簡單的設計就是如何觸發一個事件,而不是關心誰來執行它,或不同的使用者是否需要用不同的方法來關聯它。
2. 示例—簡單的事件處理
設有這樣一個類:Supplier,當它的name成員被設置就會觸發一個事件,而類Client就用于處理它。
public class Supplier
{
public Supplier() {}
public event EventHandler NameChanged; [H]
public string Name
{
get { return name; }
set { name = value; OnNameChanged(); } [H]
}
private void OnNameChanged()
{
// 在注冊后,使用者即可觸發此事件
if (NameChanged != null)
NameChanged(this, new EventArgs());
}
private string name;
}
public class Client
{
public Client()
{
// 為 supplier 事件注冊
supplier = new Supplier();
supplier.NameChanged += new EventHandler( _
this.supplier_NameChanged); [H]
}
public void TestEvent()
{
// 設置Name,產生一個事件
supplier.Name = "Kevin McFarlane";
}
private void supplier_NameChanged(object sender, _
EventArgs e) [H]
{
// 處理supplier事件
}
private Supplier supplier;
}
[Henry注:在此示例中,我們同時可以學習一下在C#中自定義一個事件,并處理它的方法。請注意我標上[H]的代碼]。
一個事件的使用者即可以是外部的,也可以是內部的。
一個“外部的”使用者是指可以執行一個事件,但與觸發此事件的類并無關系。換句話說,它不是事件類繼承樹中的一部分。示例中的Client類就是一個外部使用者。
一個“內部的”使用者可以是事件發生類自身(如果它同時處理自已的事件的話),或是該事件發生類的一個派生類。對于這種情況,上文所說的簡單的設計就不夠充分了。用戶不能很方便地改變當事件被觸發后要發生的變化,或是處理事件的默認行為了。
為了應付這個問題,在.NET Design Guidelines for Class Library Developers一文中,Microsoft推薦使用一個保護的Virtual Method(虛方法)去觸發每個事件。這就提供給子類一個通過重寫來處理事件的方法。因此,在我們的示例中,OnNameChanged()應該象下例這樣寫:
protected virtual void OnNameChanged()
{
// 在注冊后,使用者即可觸發此事件
if (NameChanged != null)
NameChanged(this, new EventArgs());
}
Microsoft隨即說:“派生類在處理OnEventName時(Henry注:OnEventName即類似于OnNameChanged的事件觸發方法),可選擇不調用基類。要這么做就得在OnEventName方法中不包含任何處理過程以利于基類正確地工作?!?/SPAN>
這有一個問題,一般來說,OnNameChanged()在觸發事件前可以做一些默認的處理。重寫OnNameChanged()可以實現不同的處理過程。但是為保證外部的使用者工作正常,它必須調用基類。如果它不能調用基類,該事件就不能為外部使用者所用。忘記調用觸發事件的基類,就違背了Liskov的多態替代原則(Henry注:出自麻省理工學院(MIT)計算機科學實驗室的Barbara Liskov女士發表的經典文章Data Abstraction and Hierarchy,本文原作者做了小改動):使用指向基類的引用的方法,必須能夠在不知道具體派生類對象類型的情況下使用它們。幸運的是,現在有一個解決的方法。
3. Template Method設計模式
Template Method設計模式的目的是定義一個算法作為固定的操作步驟,但有一個或多個步驟可以有變化。(Henry注:變化通常是指將某些步驟延遲到子類中去描述與執行)在我們的示例中,算法可認為是由觸發事件及其響應來組成。需要有變化的地方就是響應。因此決竅就在于將它從事件觸發中分離出來。我們將OnNameChanged()分割成兩個方法:InternalOnNameChanged()
和OnNameChanged()
。InternalOnNameChanged()
調用OnNameChanged()來執行默認的處理,然后觸發事件。
private void InternalOnNameChanged()
{
//派生類可重寫默認的行為
OnNameChanged();
// 在注冊后,使用者即可觸發此事件
if (NameChanged != null)
NameChanged(this, new EventArgs());
}
protected virtual void OnNameChanged()
{
//在此執行默認的行為
}
Name屬性改為:
get { return name; }
set { name = value; InternalOnNameChanged(); }
使用這種方法的好處在于:
1) 在這個觸發事件的示例中,它是基類執行的重要步驟以避免派生類調用基類執行的失敗。因此外部使用者可以獲得更為可靠的服務;
2) 派生類可毫不用擔心,在OnNameChanged()中安全地替換基類的默認行為。
e-mail: ruigeren@sina.com
QQ: 18349592
----
聲明:本文版權與解釋權歸韓睿所有,如需轉載,請保留完整的內容及此聲明。
原文轉自:http://www.anti-gravitydesign.com