• 设计模式之二 观察者模式


          首先我们来看看观察者模式的定义:对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。也就是这个模式是用来处理一对多依赖的。 这里所说的"依赖"不是我们常理解的——当你直接实例化一个对象时就是在依赖它的具体类。而是指其它多个对象要用到同一个对象中的数据。我们把多个对象可以看作是观察者,另一个对象看作是被观察者,我们这里叫主体事物。

         当然实现观察者模式的方法由很多种,按类型来分可分为:推模型和拉模型。推模型即是主体事物把观察者需要的数据发送给观察者。而拉模型即是观察者根据自己的需求向主体事物索取数据。这里我们只涉及推模型。

        来看一个简单的例子。狗叫了,小偷跑了,孩子哭了。从这句话可以看出,总共有三个类。如果不加思索的设计成这样。代码如下:

    example
    1 class child
    2 {
    3 //小孩哭
    4   public void cring()
    5 {
    6
    7 }
    8 }
    9
    10
    11 class thief
    12 {
    13 //小偷跑
    14   public void runing()
    15 {
    16
    17 }
    18 }
    19
    20 public class dog
    21 {
    22 public thief _thief = new thief();
    23 public child _child = new child();
    24 //狗叫
    25   public void barking ()
    26 {
    27 //小偷跑了
    28   _thief.runing();
    29 //小孩哭
    30   _child.cring();
    31 }
    32 }

    那么这个设计肯定是失败的。从上面的代码来看,在dog类中有两个类(thief和child)是直接实例化的。那么dog类要依赖于其它两个类。即它是针对实现编程的,并且这个设计让狗和小偷与小孩耦合在一起了,违背了高内聚低耦合的原则。如果我们要增加或删除它的功能,我们必须修改dog这个类。例如增加爸爸抓小偷。这样的话我们就要改动dog类。回想上一个模式策略模式——要把改变的地方封装起来。这样才能设计出更好的框架。在讲正确代码之前给大家看张图,一张通用的观察者模式的类图。如下。

    上图中 Subject 是一个主体事物的接口。3个方法分别为 注册观察者,注销观察者,通知观察者。Observer 是观察者接口,它有个用来作出反应的方法。下面的两个类分别继承它们。值得一提的是:实现Subject的类一定要有观察者(Observer)接口的一个列表(ArrayList)。实现Observer的类中一定要有主体事物(Subject)的一个引用。至于为什么,我们下面结合例子来讲。

            现在我们来看看用观察者模式是怎么具体实现的:从给出的那句话分析可知,小偷和小孩都是因为听到狗叫才做出反应的。即可知狗就是主体事物,小偷和孩子是观察者。它的uml用例图如下:

    上图的意思就是狗能够通知小偷和孩子。到这里这个模式的定义应该懂了点吧。但这个模式到底怎么具体实现呢?不要急我们一步一步来。

    首先整体设计:从上一设计模式知道有一个针对接口编程的原则。我们要使主体事物和观察者松耦合。我们就得把主体事物和观察者抽象成接口。看上图,要使狗能够通知小偷和小孩,那么在狗中必须要有小偷和孩子的引用,而小偷和小孩不能在狗中实例化。并且小偷的跑和孩子的哭我们可以抽象成是观察者的反应。所以我们要为主题对象定义一个接口(subject),为观察者定义一个接口(observer)。代码如下:

    Interface
    1 interface Subject
    2 {
    3 //注册观察者
    4   void registerObserver(Observer o);
    5 //注销观察者
    6   void removeObserver(Observer o);
    7 //通知观察者
    8 void notifyObserver();
    9 }
    10
    11 public interface Observer
    12 {
    13 //观察者的反应
    14 void display();
    15 }

    其实这里就是三句话,要想使主体事物和观察者松耦合就必须定义接口或抽象类。主体事物接口包括了三个基本方法,注册,注销,通知。观察者接口包括反应方法。

    接下来我们要实体化具体的类了。先来看看狗,因为它属于主体事物那么它必须实现Subject接口。而它还要给注册的观察者发送消息。那它必须要有对所有观察者的引用。因为观察者都要实现Observe接口。根据OO的多态性。所以我们要在dog中保存Observer接口的列表。又因为是狗叫的原因才引起观察者的反应,我们也要加一个barking()方法来通知观察者。代码如下:

    dog
    1 public class dog :Subject
    2 {
    3 //声明观察者列表
    4 public ArrayList Observers;
    5 public dog ()
    6 {
    7 //定义观察者列表
    8 Observers = new ArrayList();
    9 }
    10 //注册
    11 public void registerObserver(Observer o)
    12 {
    13 Observers.Add(o);
    14 }
    15 //注销
    16 public void removeObserver(Observer o )
    17 {
    18 int i = Observers.IndexOf(o);
    19 if (i >= 0)
    20 Observers.Remove(o);
    21 }
    22 //通知观察者
    23 public void notifyObserver()
    24 {
    25 foreach (var observer in Observers)
    26 {
    27 Observer observer1 = (Observer) observer;
    28 observer1.display();
    29 }
    30 }
    31 //狗叫了
    32 public void barking ()
    33 {
    34 notifyObserver();
    35 }
    36 }

    其实这里也就是两句话:实现主体事物接口的类中必须要有观察者接口的列表。另外就是观察者需要的信息(这个例子中指的就是狗叫),在这个信息中要通知所有的观察者类。

    再来看看实例化的观察者类;小偷和小孩。简单的说观察者类有两个任务。一个是注册一个是作出的反应。应为注册函数在主体事物中。那我们也要在观察者类中要有Subject的引用。 这样我们才能把自己注册到被观察的对象中去。看看下面的代码:

    Observers
    1 class child:Observer
    2 {
    3 private Subject dog;
    4 public child(Subject dog1)
    5 {
    6 this.dog = dog1;
    7 //把观察者类注册到主题事物中
    8 dog.registerObserver(this);
    9 }
    10 //实现观察者接口函数
    11 public void display()
    12 {
    13 Cring();
    14 }
    15 //小孩哭
    16 public void Cring()
    17 {
    18 Console.WriteLine("The child is cring");
    19 }
    20
    21
    22 class thief:Observer
    23 {
    24 private Subject dog;
    25 public thief (Subject dog1)
    26 {
    27 this.dog = dog1;
    28 //把观察者类注册到主题事物中
    29 dog.registerObserver(this);
    30 }
    31 //实现观察者接口函数
    32 public void display()
    33 {
    34 Runing();
    35 }
    36 //小偷跑
    37 public void Runing()
    38 {
    39 Console.WriteLine("The thief is runing");
    40 }
    41 }

    需要注意的也就两点:要用Subject的引用,用来把自己注册到主体事物的类中。具体实现自己的反应方法。

    现在我们来测试我们所写的几个类,代码:

    Text
    1 class Test
    2 {
    3 public static void Main(string [] args)
    4 {
    5 dog dog1 = new dog();
    6 child child1 = new child(dog1);
    7 thief thief1 = new thief(dog1);
    8 dog1.barking();
    9 Console.ReadKey();
    10 }
    11 }

    到这里我们的一个观察者模式就简单的讲完了。当然我这只是简单的走了一遍观察者模式的概念。

    在这个设计模式中我学到了另外一个设计原则:为了交互对象之间的松耦合设计而努力。(我的理解就是多利用接口来编程)

    总结一下:关于观察者的一切,主体事物只知道观察者实现了某个接口(也就是Observer)。主体事物不需要知道观察者的具体类是谁、做了些什么活其他任何细节。任何时候我们可以增加新的观察者。因为主体事物唯一依赖是一个实现Observer接口的对象列表。

    PS:对于这拉模型的例子,大家可以看看其他的资料。

  • 相关阅读:
    IP的幻觉
    糟糕的一天
    windows下批量生成文件
    基于Bandersnatch搭建本地pypi源
    vmware vsphere 无法启动故障;
    关于Centos7客户端代理配置
    怎样在交换机判断是否出现环路了呢?
    小小的网络故障
    express for LINUX
    ESXI 7.0 ovf 导出;
  • 原文地址:https://www.cnblogs.com/7579/p/1989770.html
Copyright © 2020-2023  润新知