委托的应用之二:使用多播委托编码Observer模式。以及事件的引入
2011-09-22 15:43
801 查看
下面将通过一个例子来讲解如何使用多播委托编码Observer模式。
问题描述:一个加热器和一个冷却器连接到同一个自动调温器。为了控制加热器和冷却器的打开和关闭,要向它们通知温度的变化。自动调温器将温度的变化发布给多个订阅者:加热器和冷却器。
(1)定义订阅者:加热器和冷却器 的方法
![](http://hi.csdn.net/attachment/201109/22/0_1316674517uan1.gif)
![](http://hi.csdn.net/attachment/201109/22/0_1316674538vqoD.gif)
(2)定义发布者类:自动调温器
![](http://hi.csdn.net/attachment/201109/22/0_1316674595h51F.gif)
(3)在主程序中连接发布者和订阅者,改变自动调温器的当前温度。
![](http://hi.csdn.net/attachment/201109/22/0_1316674694tw97.gif)
上述使用多播委托来编码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、封装订阅
错误地使用“=”而不是“+=”
上述代码与原来代码的唯一区别,就是它不是使用“+=”运算符,而是使用一个简单的赋值运算符。其结果就是将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委托。
问题描述:一个加热器和一个冷却器连接到同一个自动调温器。为了控制加热器和冷却器的打开和关闭,要向它们通知温度的变化。自动调温器将温度的变化发布给多个订阅者:加热器和冷却器。
(1)定义订阅者:加热器和冷却器 的方法
![](http://hi.csdn.net/attachment/201109/22/0_1316674517uan1.gif)
![](http://hi.csdn.net/attachment/201109/22/0_1316674538vqoD.gif)
(2)定义发布者类:自动调温器
![](http://hi.csdn.net/attachment/201109/22/0_1316674595h51F.gif)
(3)在主程序中连接发布者和订阅者,改变自动调温器的当前温度。
![](http://hi.csdn.net/attachment/201109/22/0_1316674694tw97.gif)
上述使用多播委托来编码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委托。
相关文章推荐
- c#中的委托、事件、Func、Predicate、Observer设计模式以及其他
- .Net中使用事件和委托实现Observer模式(一)
- Observer 设计模式 了解委托与事件的使用
- 委托、事件、Observer观察者模式的使用解析二
- 使用委托和事件实现观察者模式(Observer Pattern)
- c#中的委托、事件、Func、Predicate、Observer设计模式以及其他
- 委托、事件、Observer观察者模式的使用解析一
- c#中的委托、事件、Func、Predicate、Observer设计模式以及其他
- C#设计模式之十九:Observer模式(使用委托、事件)
- C#中使用委托和事件实现观察者模式(observer pattern)
- 使用委托和事件实现观察者模式(Observer Pattern)
- c#中的委托、事件、Func、Predicate、Observer设计模式以及其他
- [学习笔记]c#中的委托、事件、Func、Predicate、Observer设计模式以及其他
- C#中使用委托和事件实现观察者模式(observer pattern)
- 委托, 匿名委托, Lambda表达式, 事件的本质, 以及Observer模式.
- Observer 设计模式(委托和事件的使用)
- .Net中使用事件和委托实现Observer模式(二)
- C# ~ 从 委托事件 到 观察者模式 - Observer
- C#,由委托到Observer设计模式,再到事件机制
- 在C#中使用代理(即委托)的方式触发事件(观察者模式)