Henry手记—使用Template Method设计模式的
**.NET事件处理机制(一) **
** By ** ** Kevin McFarlane **
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 方法中不包含任何处理过程以利于基类正确地工作。”
这有一个问题,一般来说, 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(); }
使用这种方法的好处在于:
在这个触发事件的示例中,它是基类执行的重要步骤以避免派生类调用基类执行的失败。因此外部使用者可以获得更为可靠的服务;
派生类可毫不用担心,在 OnNameChanged() 中安全地替换基类的默认行为。
e-mail: [email protected]
QQ: 18349592
----
声明:本文版权与解释权归韩睿所有,如需转载,请保留完整的内容及此声明。