书接上文,委托是.Net Framework提供的类型安全的回调机制。委托本质上是类,类里面实现了带有方法指针的构造函数、Invoke、BeginInvoke和EndInvoke四个方法。当然这些工作都是CLR和编译器帮助我们完成的。事件建立在委托的基础上。CLR偷偷地帮我们把一个事件转换为一个私有字段和两个公有方法。一个私有字段是私有委托字段,两个公共方法是对委托字段的增加和移除的线程安全调用。本文重点不在于讨论委托和事件机制,而是通过事件如何方便的实现项目要求的内容。
为了方便论述,我再次描述项目要求。
有一个项目,有压力传感器、位移传感器、震动传感器等多种传感器每种类型有若干个传感器,对每个传感器做特定操作会引发历史曲线图、实时柱状图、实时数据表、状态显示等多种展示功能。
那么我们同样以测试驱动的方式先实现压力传感器通过特定操作实现实时数据表的功能。
测试代码段跟Observer拉模式是一样的,参见代码清单1,功能实现为代码清单2、3、4。
代码清单1 PressureSensorAndDataTableTest.cs
namespace RTChangeData
{
[TestFixture]
class PressureSensorAndDataTableTest
{
[Test]
public void PressureSensorChanged()
{
PressureSensorSubject ps = new PressureSensorSubject(5);
DataTableObserver dt = new DataTableObserver(ps);
ps.Channel1Data = 1;
ps.Channel2Data = 2;
Assert.AreEqual(ps.ID, dt.ID, "ID Not Equal.");
Assert.AreEqual(ps.Channel1Data, dt.Channel1Data, "Channel1 Data Not Equal.");
Assert.AreEqual(ps.Channel2Data, dt.Channel2Data, "Channel2 Data Not Equal.");
}
}
}
代码清单2 Subject.cs
namespace RTChangeDataByEvent
{
public interface Subject
{
event EventHandler
Channel1DataChanged;
event EventHandler
Channel2DataChanged; } public class PSEventArgs : EventArgs { public int NewValue { get; set; } public int ID { get; set; } public PSEventArgs(int id, int newValue) { ID = id; NewValue = newValue; } } }
代码清单3 PressureSensorSubject.cs
namespace RTChangeDataByEvent
{
public class PressureSensorSubject : Subject
{
private int m_id;
private int m_channel1Data;
private int m_channel2Data;
public int Channel1Data
{
get
{
return m_channel1Data;
}
set
{
m_channel1Data = value;
OnChannel1DataChanged(new PSEventArgs(m_id, value));
}
}
public int Channel2Data
{
get
{
return m_channel2Data;
}
set
{
m_channel2Data = value;
OnChannel2DataChanged(new PSEventArgs(m_id, value));
}
}
public object ID { get; set; }
public event EventHandler
Channel1DataChanged;
public event EventHandler
Channel2DataChanged; public PressureSensorSubject(int id) { this.m_id = id; } protected virtual void OnChannel1DataChanged(PSEventArgs e) { //以线程安全的方式调用 EventHandler
tmp = Interlocked.CompareExchange
>(ref Channel1DataChanged, null, null); if (tmp != null) { tmp.Invoke(this, e); } } protected virtual void OnChannel2DataChanged(PSEventArgs e) { //以线程安全的方式调用 EventHandler
tmp = Interlocked.CompareExchange
>(ref Channel2DataChanged, null, null); if (tmp != null) { tmp.Invoke(this, e); } } } }
代码清单4 DataTableObserver.cs
namespace RTChangeDataByEvent
{
class DataTableObserver
{
public object ID { get; set; }
public object Channel1Data { get; set; }
public object Channel2Data { get; set; }
public DataTableObserver(Subject ps)
{
ps.Channel1DataChanged += new EventHandler
((sender, e) =>
{
this.ID = e.ID;
Channel1Data = e.NewValue;
});
ps.Channel2DataChanged += new EventHandler
((sender, e) => { Channel2Data = e.NewValue; PressureSensorSubject pss = sender as PressureSensorSubject; if (pss != null) { ID = pss.ID; } }); } } }
是不是发现类结构跟Observer很不一样?新技术往往能够放过来影响设计方式。下面先描述类图再来讲述采用事件实现的特点。

图1 事件观察者模式
从图1中可以看出,增加了参数类PSEventArgs,去掉了Observer类,而且倒置了Observer与Subject之间的依赖关系。同时DataTableObserver依赖于接口满足DIP原则。需要特殊说明的是在DataTableObserver中应该存在两个函数,这两个函数实现了触发Subject相应事件时应该执行的操作。在代码4中使用的lambda表达式。当短短两三行就能实现函数时使用lambda表达式是优雅直观的,如