设计模式之二 观察者模式

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

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

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

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:对于这拉模型的例子,大家可以看看其他的资料。

原文地址:https://www.cnblogs.com/7579/p/1989770.html