.net 委托(委托链)的若干"陷阱" [续]——以委托为载体的观察者模式
陷阱2:“调用委托就像调用方法一样简单”
上一篇博文分析了设计模式中“观察者模式”常被忽略的两个细节:“异常”和“阻塞”。本文将使用委托重写上文“观察者模式”中的小故事。同时和大家一起看看,调用委托是不是就像调用方法一样简单。
[观察者模式中"主题类"代码示例]
/// <summary> /// 通过委托发送通知的主题 /// </summary> public class DelegateSubject { //委托定义 public delegate void NotifyEventHandler(string msg); //委托实例(观察者接收消息的委托实例) public NotifyEventHandler NotifyObservers = null; //主题名称:如天气 public string SubjectName { get; set; } //发布的消息 public string Message { get; set; } //主题构造函数 public DelegateSubject(string subjectName) { SubjectName = subjectName; } //通过委托发送通知(调用观察者接收消息的方法) public void Notify() { NotifyEventHandler tempNotifyHander = NotifyObservers; if (tempNotifyHander != null) { //调用委托就像调用方法一样简单。?
tempNotifyHander(Message); } } }
[观察者模式中"观察者类"代码示例]
/// <summary> /// 具体观察者 /// </summary> public class ConcreteObserver : IObserver { //观察者名称:如学校、公司 public string ObserverName { get; set; } //观察者能识别的天气 string[] weather = { "毛毛雨", "大雨","晴天" }; //构造观察者对象 public ConcreteObserver(string observerName) { ObserverName = observerName; } //收到天气主题发来的通知 public void NewMessage(string msg) { if (weather.Contains(msg)) //观察者能识别的天气 { Console.WriteLine(ObserverName + "收到新通知:" + msg); Thread.Sleep(2000); Console.WriteLine(ObserverName + "收到新通知,处理了2s"); } else //突然来了鬼天气... { Console.WriteLine(ObserverName + "收到新通知:" + msg); Console.WriteLine(ObserverName + "被吓坏了,没见过这鬼天气!!!乱套了..."); throw new Exception(ObserverName + "没处理好" + msg + ",出异常了"); } } }
[测试代码示例]
private static void TestDelegateObserverPattern() { try { DelegateSubject subject_Weather = new DelegateSubject("天气"); IObserver observer_School = new ConcreteObserver("学校"); IObserver observer_Company = new ConcreteObserver("公司"); subject_Weather.NotifyObservers += observer_School.NewMessage; subject_Weather.NotifyObservers += observer_Company.NewMessage; subject_Weather.Message = "毛毛雨"; subject_Weather.Notify(); Console.WriteLine(); subject_Weather.Message = "飓风"; subject_Weather.Notify(); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
看过上篇博文的话,具体的测试结果大家可能已心中有数。即当主题发布“毛毛雨”时,学校和公司两个主题依次收到了通知。主题发布“飓风”消息时,学校收到了通知,而公司却没有。如下图:
本例中主题使用委托作为载体,发送消息通知。如DelegateSubject.Notify方法所示,发送新消息时调用委托实例 tempNotifyHander(Message)。可以看出委托的调用形式和调用方法一样(但形似神不似)。在这里,委托实例上注册了两个观察者的NewMessage方法,形成了委托链。在委托实例被调用时,委托链中的方法依次调用。
结论:
调用委托并不像调用方法一样简单,一个委托实例调用后,可以执行委托链上的多个方法;而方法仅仅是方法。
让主题的通知更健壮:
将委托像方法一样进行调用,这在用法上是没有问题的。但如果你的系统对健壮性有要求,需要你把握委托调用的内部细节(如保证即使“飓风”来了,学校和公司都能收到通知)。这时,就要不能“像调用方法一样简单地调用委托了”。可以尝试通过Delegate.GetInvocationList方法获取委托实例上注册的委托链,然后进行自己的处理。一个简单的处理实例如下:
//通过委托发送通知(调用观察者接收消息的方法) public void Notify() { NotifyEventHandler tempNotifyHander = NotifyObservers; if (tempNotifyHander != null) { //tempNotifyHander(Message); //获取委托链 Delegate[] chain = tempNotifyHander.GetInvocationList(); foreach (Delegate item in chain) { try { item.DynamicInvoke(new object[] { Message });//依次调用委托进行通知 } catch (Exception ex) //某个观察者接收到消息后异常了 { Console.WriteLine(ex.InnerException.Message);//记录异常信息 continue; //继续通知下一观察者 } } } }
ps:对应观察者接收事件中的阻塞问题,可以使用Delegate.BeginInvoke方法进行处理。