.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方法进行处理。

posted on 2013-05-28 21:17  marshal-m  阅读(527)  评论(1编辑  收藏  举报

导航