zoukankan      html  css  js  c++  java
  • 重温设计模式之观察者

    之前看过一些设计模式的书和文章,像head first,大话设计模式,不过一直都是囫囵吞枣,一知半解,

    时间一长,大多忘了个十之八九,和没看过竟然相差无几,这实在是件很让人伤感的事。

    这情形就好像你看过一部很经典的电影,有一天和女神聊天,女神说:我看过一部很经典的电影………,

    然后神情的忘着你,想和你交流下那部电影的观后感,而此时,你的心里确是一片空白,你甚至会狠咬自己一口看能否从

    疼痛中挤出点印象来,因为你知道,和女神聊电影的机会可是不多的,

    可是很可惜,你还是错过了,于是你镇痛过后,决定再次重温,将其记录下来!

    闲话太多了,下面切入正题,此处涉及如下几个问题:

    1 观察者模式的定义?

    2 它主要应用在何处?

    3 示例?

    4 剩下的问题?

    1 观察者模式的定义:

    此模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。[GOF 《设计模式》]

    说白点意思就是:假如有对象a,b,c,当a发生变化时,需要对象b,c得到通知,并做相应的变化,那么此情形就是一个观察者模式。

    2 应用在何处?

    举几个例子:

    (1)以去银行柜台取钱为例,步骤应该是你先去拿号然后等着显示屏上显示你拿的号码了,就过去柜台取钱,这是一个很经典的观察者模式。

    柜台办理完业务后通知显示屏,然后显示屏更新信息,这里类图关系如下:

    柜台类a,显示屏b,c,d等,类a变化时通知b,c,d,然后b,c,d等收到变更通知后更新信息

    (2)以天气预报为例,气象台取得最新天气信息后,通知所有的电视台播报员,然后播报员预播最新的天气信息,

    类图关系如:气象台a,电视台b,c,d等,气象台a观察到天气信息有变化,通知b,c,d天气有变,然后b,c,d播报最新天气信息

    3 此时似乎还是一知半解,来看示例吧!

    以银行柜台显示屏为例来看,我们先从最简单的实现代码开始,逐渐修改成观察者模式实现方式:

    (1)先从实现如上需求最简单的代码开始,如下:

     /// <summary>
        /// 柜台
        /// </summary>
        public class Counter
        {
            private List<Screen> lstScreen = new List<Screen>();//显示屏列表
            private string counterName;//柜台名称
            private string bussinessNo;//当前业务号
    
         public Counter(string name,string no)
         {
             this.counterName = name;
             this.bussinessNo = no;
         }
    
            //增加需要通知的屏幕
          public void AddScreen(Screen Screen)
          {
                lstScreen.Add(Screen);
          }
    
          //删除需要通知的屏幕
          public void RevScreen(Screen Screen)
          {
             lstScreen.Remove(Screen);
          }
    
            //柜台业务发生变化,通知显示屏更新信息
            public void NotifyChange()
            {
                foreach(Screen Screen in lstScreen )
                {
                    Screen.Display();
                }
            }
    
            //通知显示屏要更新的信息
            public string GetInformation()
            {
                return "请"+bussinessNo+"号到"+ counterName+"号柜台办理业务!";
            }
    
    
        }
    
        /// <summary>
        /// 显示屏
        /// </summary>
        public class Screen
        {
            private string name;
            private Counter counter;
    
            public Screen(string name,Counter counter)
            {
                this.name = name;
                this.counter = counter;
            }
    
            public void Display()
            {
                Console.WriteLine(this.name+":"+counter.GetInformation());
            }
        }
    

    上面有2个类,柜台类和显示屏类,业务类调用代码如下:

      static void Main(string[] args)
            {
                Counter counter = new Counter("1号柜台","业务号9");
                Screen scr1 = new Screen("1号显示屏", counter);
                Screen scr2 = new Screen("2号显示屏", counter);
                counter.AddScreen(scr1);
                counter.AddScreen(scr2);
    
                counter.NotifyChange();
            }
    

     执行过程:先实例化柜台类,然后将要通知显示的显示屏加到通知列表,然后调用通知的方法,

    显示屏更新的过程在同志的 方法里实现。上述过程有个显而易见的问题就是,双向耦合调用。

     
     public void NotifyChange()
            {
                foreach(Screen Screen in lstScreen )
                {
                    Screen.Display();
                }
            }
    
    
      public void Display()
            {
                Console.WriteLine(this.name+":"+counter.GetInformation());
            }

    柜台类的通知方法

     NotifyChange()
    

    里调用了显示屏更新信息的方法,而显示屏更新信息

    Display()
    

    的方法又调用了柜台类获取通知信息的方法。

     (2)若此时需求出现变更,除了显示屏,还需要用广播通知,即柜台办理完业务后,需要用广播更新信息,

    业务你想到了,再新建一个广播类(和显示屏类类似),然后在柜台类最如下修改

      //柜台业务发生变化,通知显示屏更新信息
            public void NotifyChange()
            {
                foreach(Screen Screen in lstScreen )
                {
                    Screen.Display();
                }
    遍历广播类,通知其更新信息         }

    我们增加了一个广播类Speaker,还修改了类Counter,,当前提出的需求算是解决了,可是代码的耦合度却加大了,已经有3个类纠缠在一起了,那么以后再追加接收信息的终端怎么办?每一次追加观察者,都不得不改一遍类Counter的代码,如果继续做下去,出错的几率将不断加大,可维护性不断降低,这段代码明显违背开闭原则,所以,我们需要重新审视之前的设计了。

    此时也许你想到了抽象出一个类作为显示屏,和广播类的基类。面向对象的设计中最重要的思维就是抽象。显然,无论是小屏、大屏还是音箱,它们只是外观和更新信息的手段有所不同,更新信息的功能却是一致的,所以,我们完全可以把这些观察者抽象出来一个基类Observer,更新信息的手段由各个观察者自己负责实现。

      abstract public class Observer
        {
            protected String name;
            protected Counter counter;
            //构造函数
            public Observer(String name, Counter counter)
            {
                this.name = name;
                this.counter = counter;
            }
            //更新信息
            public abstract void update();
        }

     然后柜台类作如下修改:

     private List<Observer> lstScreen = new List<Observer>();
    

    经过我们这么一番改造,应对观察者的加入是绝对没有问题了,再也不用去修改柜台类Counter了,把柜台类对具体终端的依赖关系给去除了。不管是小屏还是音箱,柜台类一视同仁,进行同样的处理,它根本不需要知道需要通知的对象是什么。比如:银行又提出来加入大屏的显示。这样的需求,对于我们来说已经是很easy的事情了,可以从Observer类再派成出来一个大屏类LargeScreen。有人问:“大屏和小屏还不一样,为什么不用一个类来表示呢”。其实,做过排队项目的人可能很清楚,大屏幕的显示驱动和显示方式可能与小屏完全不同,甚至于供货厂商都不一样,在这个案例中,我们只是剥离出来它的其中一项显示功能而已。又比如:银行为了提升服务质量,准备加入短信提醒用户的功能。现在,我们是不是很容易对付了?

    (3)银行又提出了新的需求:“除了柜台上可以发布呼号信息以外,银行内部的管理部门在某些情况下,也能利用大小屏发布一些紧急的信息”。 我们按照处理观察者方法,可以把这些通知者抽象出来,形成一个基类Subject,银行柜台和管理部门作为两个通知者,都可以从基类Subject派生出来,以后再增加通知者,就能够以此类推。我们按照这个思路形成最终一个版本:

    抽象出柜台类的基类

    public abstract class Subject {
        //观察者列表
         private List<Screen> lstScreen = new List<Screen>();      //增加需要通知的屏幕
          public void AddScreen(Screen Screen)
          {
                lstScreen.Add(Screen);
          }
    
          //删除需要通知的屏幕
          public void RevScreen(Screen Screen)
          {
             lstScreen.Remove(Screen);
          }
    
            //柜台业务发生变化,通知显示屏更新信息
            public void NotifyChange()
            {
                foreach(Screen Screen in lstScreen )
                {
                    Screen.Display();
                }
            }    
     //通知显示屏要更新的信息
            public string GetInformation()
            {
                return ""+bussinessNo+"号到"+ counterName+"号柜台办理业务!";
            }
    
    }

    柜台类等通知类继承此基类,其它地方无需更改,调用如下:

              Counter counter = new Counter("1号柜台","业务号9");
                Screen scr1 = new Screen("1号显示屏", counter);
                Screen scr2 = new Screen("2号显示屏", counter);
                counter.AddScreen(scr1);
                counter.AddScreen(scr2);
    
                counter.NotifyChange();
    
                Speaker speaker = new Speaker("1号广播", "业务号9");
                Screen scr1 = new Screen("1号显示屏", speaker);
                Screen scr2 = new Screen("2号显示屏", speaker);
                speaker.AddScreen(scr1);
                speaker.AddScreen(scr2);
    
                speaker.NotifyChange();
    

    又经过我们的一番改造,应对主题和观察者的需求变化都没有什么问题了,以后针对类似这样的需求,我们已经非常有经验了,完全可以把这段代码稍加修改套用一下就可以了。换句话说,我们把观察者模式的实现方式做成了一个很小的框架。

    参考博客:http://blog.csdn.net/wanghao72214/article/details/4017507

    http://kb.cnblogs.com/page/49989/

  • 相关阅读:
    (dfs)codeforces 767C
    配置vscode c/c++像sublime那样按Ctrl+shift+B编译直接运行
    (JAVA)使用swing组件模仿QQ界面+网络编程实现QQ消息传输
    (dp)hihocoder
    (暴力+精度)hihocoder
    区域赛43天
    区域赛第33天
    区域赛41天
    最小和最大表示法
    HDU 6166 二进制划分集合
  • 原文地址:https://www.cnblogs.com/jangwewe/p/3009355.html
Copyright © 2011-2022 走看看