12.2 解 决 方 案

12.2.1 使用观察者模式来解决问题

用来解决上述问题的一个合理的解决方案就是观察者模式。那么什么是观察者模式呢?

1.观察者模式的定义

定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

2.应用观察者模式来解决的思路

在前面描述的订阅报纸的例子里面,对于报社来说,在一开始,它并不清楚究竟有多少个订阅者会来订阅报纸,因此,报社需要维护一个订阅者的列表,这样,当报社出版报纸的时候,才能够把报纸发放到所有的订阅者手中。对于订阅者来说,订阅者也就是看报的读者,多个订阅者会订阅同一份报纸。

这就出现了一个典型的一对多的对象关系,一个报纸对象,会有多个订阅者对象来订阅;当报纸出版的时候,也就是报纸对象改变的时候,需要通知所有的订阅者对象。那么怎么来建立并维护这样的关系呢?

观察者模式可以处理这种问题。观察者模式把这多个订阅者称为观察者:Observer,多个观察者观察的对象被称为目标:Subject。

一个目标可以有任意多个观察者对象,一旦目标的状态发生了改变,所有注册的观察者都会得到通知,然后各个观察者会对通知作出相应的响应,执行相应的业务功能处理,并使自己的状态和目标对象的状态保持一致。

12.2.2 观察者模式的结构和说明

观察者模式的结构如图12.3所示。

图片

图12.3 观察者模式的结构示意图

■ Subject:目标对象,通常具有如下功能。

◆ 一个目标可以被多个观察者观察。

◆ 目标提供对观察者注册和退订的维护。

◆ 当目标的状态发生变化时,目标负责通知所有注册的、有效的观察者。

■ Observer:定义观察者的接口,提供目标通知时对应的更新方法,这个更新方法进行相应的业务处理,可以在这个方法里面回调目标对象,以获取目标对象的数据。

■ ConcreteSubject:具体的目标实现对象,用来维护目标状态,当目标对象的状态发生改变时,通知所有注册的、有效的观察者,让观察者执行相应的处理。

■ ConcreteObserver:观察者的具体实现对象,用来接收目标的通知,并进行相应的后续处理,比如更新自身的状态以保持和目标的相应状态一致。

12.2.3 观察者模式示例代码

(1)先来看看目标对象的定义。示例代码如下:

732ed05acc3549bbb66241bc0db479cb

(2)接下来看看具体的目标对象。示例代码如下:

96e30f6830494e2e9356774ebab54c89

f1cdc6fed8704307a5d4ebbb9591685b

(3)再来看看观察者的接口定义。示例代码如下:

254273b4a6fe49dd878c84397a748ef3

(4)最后来看看观察者的具体实现示意。示例代码如下:

f9d1dd7dd15a437eadd2d91bbd9ea75c

03a5161145e244ce9cf32f25c5b225c0

12.2.4 使用观察者模式实现示例

要使用观察者模式来实现示例,那就按照前面讲述的实现思路,把报纸对象当作目标,订阅者当做观察者,就可以实现出来了。

使用观察者模式来实现示例的结构如图12.4所示。

图片

图12.4 使用观察者模式来实现示例的结构示意图

还是来看看具体的代码实现。

1.被观察的目标

在前面描述的订阅报纸的例子里面,多个订阅者都是在观察同一个报社对象,这个报社对象就是被观察的目标。这个目标的接口应该有些什么方法呢?还是从实际入手去想,看看报社都有些什么功能。报社有以下最基本的功能。

■ 注册订阅者,也就是说很多个人来订报纸,报社肯定要有相应的记录才行。

■ 出版报纸,这个是报社的主要工作。

■ 发行报纸,也就是要把出版的报纸发送到订阅者手中。

■ 退订报纸,当订阅者不想继续订阅了,可以取消订阅。

上面这些功能是报社最基本的功能,当然,报社还有很多别的功能,为了简单,这里就不再去描述了。因此报社这个目标的接口也应该实现上述功能,把它们定义在目标接口里面。示例代码如下:

6e45de00d89d42f8be1db26f7ce7a178

fc1935fd562d4118a68ef44dfc8f6c6c

细心的朋友可能会发现,这个对象并没有定义出版报纸的功能,这是为了让这个对象更加通用,这个功能还是有的,放到具体的报纸类中了。下面就来具体地看看具体的报纸类的实现。

为了演示的方便,在这个实现类中增添一个属性,用它来保存报纸的内容,然后增添一个方法来修改这个属性,修改这个属性就相当于出版了新的报纸,并且同时通知所有的订阅者。示例代码如下:

db152206006b4755a234371e56a85cb4

3726ed419e3f4b28884eb28b40585bd3

2.观察者

目标定义好以后,接下来把观察者抽象出来,看看他应该具有什么功能。分析前面的描述,发现观察者只要去邮局注册了以后,等着接收报纸就可以了,没有其他的功能了。那么就把这个接收报纸的功能抽象成为更新的方法,从而定义出观察者接口来。

示例代码如下:

469257fbf65b46c9b0853aefdf02082e

定义好观察者的接口以后,该来想想如何实现了。具体的观察者需要实现:在收到被通知的内容后,自身如何进行相应处理的功能。为了演示的简单,收到报纸内容以后,简单地输出一下,表示收到了就可以了。

定义一个简单的观察者实现。示例代码如下:

36957b74d789461da42c697614fcc8fe

3.使用观察者模式

前面定义好了观察者和观察的目标,那么如何使用它们呢?

那就写个客户端,在客户端里面,先创建好一个报纸,作为被观察的目标,然后多创建几个读者作为观察者,当然需要把这些观察者都注册到目标里面去,接下来就可以出版报纸了。具体的示例代码如下:

5832fc48df8a4a7699795ce9cb278f9d

ab8cec9fb721432a8fbfaaa985c1ff8d

测试一下看看,输出结果如下:

373c91eda82841d192737be8c7816ff9

你还可以通过改变注册的观察者,或者是注册了又退订,来看看输出的结果。会发现没有注册或者退订的观察者是收不到报纸的。

如同前面的示例,读者和报社是一种典型的一对多的关系,一个报社有多个读者,当报社的状态发生改变,也就是出版新报纸的时候,所有注册的读者都会得到通知,然后读者会拿到报纸,读者会去阅读报纸并进行后续的操作。