您的位置:首页 > 其它

委托的应用之二:使用多播委托编码Observer模式。以及事件的引入

2011-09-22 15:43 801 查看
下面将通过一个例子来讲解如何使用多播委托编码Observer模式。

问题描述:一个加热器和一个冷却器连接到同一个自动调温器。为了控制加热器和冷却器的打开和关闭,要向它们通知温度的变化。自动调温器将温度的变化发布给多个订阅者:加热器和冷却器。

(1)定义订阅者:加热器和冷却器 的方法





(2)定义发布者类:自动调温器



(3)在主程序中连接发布者和订阅者,改变自动调温器的当前温度。



上述使用多播委托来编码Observer模式时存在以下问题:

问题1、在发布者类Thermostat中当当前温度值改变而调用委托方法时,没有检查委托变量是否为空。

假如当前没有订阅者注册接收通知,则OnTemperatureChange为空,执行OnTemperatureChange(value)语句会引发一个NUllReferenceException。因此需要将类Thermostat中CurrentTemperature属性的 set访问器代码做如下修改:

set

{

if ( value!=CurrentTemperature )

{

_currentTemperature = value;

TemperatureChangeHandler localOnChange= OnTemperatureChange;

if(localOnChange!=null)

localOnChange(value);

}

}

说明:并没有在一开始就检查委托变量OnTemperatureChange是否为空值,而是先将OnTemperatureChange赋值给另一个委托变量localOnChange。这个简单的修改可以确保在检查空值和发送通知之间,假如所有OnTemperatureChange订阅者都被移除(由一个不同的线程),那么不会触发NUllReferenceException异常。

再次提醒:在调用一个委托之前,要检查它的值是否为空。

问题2:假如一个订阅者引发了一个异常,那么后续的订阅者就接收不到发布者发出的通知。

由于委托的调用列表中的方法是顺序调用而不是同时调用的,所以当一个订阅者引发了异常,那么后续的订阅者就接收不到发布者发出的通知。为了避免这个问题,使所有的订阅者都能收到通知(不管之前的订阅者有过什么行为),必须手动遍历订阅者列表,并单独调用它们。

将类Thermostat中CurrentTemperature属性的 set访问器代码做如下修改:

set

{

if ( value!=CurrentTemperature )

{

_currentTemperature = value;

TemperatureChangeHandler localOnChange= OnTemperatureChange;

if(localOnChange!=null)

{

foreach ( TemperatureChangeHandler handler in localOnChange.GetInvocationList() )

{

try

{ handler(value); }

catch (Exception exception)

{ Console.WriteLine(exception.Message);}

}

}

}

}

代码说明:从一个委托的GetInvocationList()方法可以获得一份订阅者列表。枚举该列表中的每一项,可以返回单独的订阅者。若随后将每个订阅者调用都放到一个try/catch块中,就可以先处理好任何出错的情形,再继续循环迭代。这样尽管某个订阅者引发了异常,随后的订阅者也能收到温度改变的通知。

问题3、当委托方法有返回值或参数为引用类型时,也必须遍历委托调用列表,而并非直接激活一个通知。

因为调用一个委托,就有可能造成将一个通知发给多个订阅者。假如订阅者方法有返回值,就无法确定应该使用哪一个订阅者的返回值。类似地,当委托方法的参数为引用类型时也需要特殊处理。

事件的作用

到目前为止使用的委托存在两个关键的问题。c#使用关键字event来解决这些问题。

1、封装订阅

错误地使用“=”而不是“+=”

class Program
{
public static void Main()
{
Thermostat thermostat = new Thermostat();
Heater heater = new Heater(60);
Cooler cooler = new Cooler(80);
string temperature;

thermostat.OnTemperatureChange = heater.OntemperatureChanged;

//bug: assignment operator overrides previous assignment
thermostat.OnTemperatureChang = cooler.OnTemperatureChanged;
}


上述代码与原来代码的唯一区别,就是它不是使用“+=”运算符,而是使用一个简单的赋值运算符。其结果就是将cooler.OnTemperatureChanged赋值给OnTemperatureChange时,heater.OnTemperatureChanged会被移除,因为一个全新的委托链替代了之前的链。在本该使用“+=”的地方使用了“=”,由于这是一个十分容易犯的错误,所以最好的办法就是根本不为包容类(声明委托的类)以外的对象提供对赋值运算符的支持。event关键字的目的就是提供额外的封装,避免你不小心的取消其他订阅者。

2、封装发布

委托和事件的第二个重要区别在于,事件确保只有包容类才能触发一个事件通知。

class Program

{

public static void Main()

{

Thermostat thermostat = new Thermostat();

Heater heater = new Heater(60);

Cooler cooler = new Cooler(80);

string temperature;

thermostat.OnTemperatureChange = heater.OntemperatureChanged;

//bug: assignment operator overrides previous assignment

thermostat.OnTemperatureChang+ = cooler.OnTemperatureChanged;

thermostat .OnTemperatureChange(42);

}

}

上述代码 从事件包容者外部触发了事件。即使thermostat的CurrentTemperature没有发生变化,Program也能调用OnTemperatureChange 委托。因此,Program触发了对所有thermostat订阅者的一个通知,告诉它们温度已发生改变,而实际上thermostat的温度没有变化。和之前问题一样,委托的问题在于封装的不充分。Thermostat应禁止其他任何类调用OnTemperatureChang委托。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: