java 观察者设计模式

是什么?为什么使用观察者设计模式?

参考:http://blog.csdn.net/monkey_d_meng/article/details/5701998

 

一、是什么

观察者模式又叫发布-订阅(Publish/Subscribe)模式。

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

换一种说法:观察者和被观察者之间存在“观察”的逻辑关联,当被观察者发生改变的时候,观察者就会观察到这样的变化,并且做出相应的响应。

举个例子:

xie和peng两个同学在考试,时刻留意着监考老师是否发出“考试结束”的信号(观察者同时监听某一主题对象)。一旦考试时间到(主题对象的状态发生变化),监考老师就会说:“考试结束”(会通知所有观察者对象),两个同学就把试卷交上去(观察者自行响应变化)。

具体实现为:

Observer.java

写一个观察者抽象类,类中有一个update方法,用于响应监听对象的变化。再写一个实际的观察者类,实现观察者类。

Subject.java

写一个事件接口,接口中可以对观察者进行操作(attach,detach)。再写一个实现类Teacher,用于发起动作(宣布考试结束)。

Main.java

先创建监听的事件对象(teacher),再创建监听者对象(xie、peng),再加入事件对象的监听队列中,决定哪些监听者受事件影响(attach),最后事件发生变化(announce),而监听者也随之进行相应的处理。

运行结果为:

老师一宣布考试结束,xie和peng马上就做出反映,进行交卷。

从上面的例子可以看出,观察者模式的基本结构为:

777

二、为什么使用观察者设计模式

在上面的例子中,如果不使用观察者模式,就要写成这样:

Main.java

那么问题来了:假如老师不在,需要班长来结束考试,怎么办?

班长并不是老师,不能继承老师类。我们只好再实现一个班长类Monitor,再把所有关于老师的类删掉:

这么一个简单的问题,我们不但要新建一个班长类,还要把Main、Studnet、Teacher全部改掉。

那么问题又来了:老师想测试一下这次的试卷难度如何,于是和学生一起考试,一起交卷。但是,老师不是学生,不能继承学生类…如果再独立实现一个“假扮学生的老师”类,又要把一系列类全都改动一遍…

这就是“直接调用”导致耦合度太高的弊端,很难对代码进行改动。个人认为,观察者模式的目的就是为了解耦:

(1)第一个情景:首先新建一个班长类,让其实现事件接口(因为班长要宣布考试结束(即事件发生变化)),然后把所有学生对象加入班长类的监听队列中。只要班长宣布考试结束,监听中的所有学生对象都会发生变化。

(2)第二个情景:让老师继承观察者抽象类(实现抽象类中的方法),然后把老师对象自身加入到老师类的监听对象中。老师既是观察者,又是事件。

综上,当一个对象的改变需要同时改变其他对象的时候,而且它不知道(或者说不需要知道?)具体有多少对象有待改变时,应该考虑使用观察者模式。

三、实际例子

现在有这样一个需求:多个游戏客户端监听游戏服务端即时发布的公告。如果游戏服务端的公告更新了,客户端就需要更新公告。

有这样两种解决方案:

1.轮询

服务端更新公告,进行缓存,然后客户端轮询获取公告(比如每分钟获取一次),然后进行更新。

简单一想就会发现很多细节问题:

(1)客户端定时轮询可能会形成访问高峰(某时刻访问量剧增,服务端压力很大),而高峰过后很长一段时间都没有请求(没有请求,服务端空闲,没有充分利用)。

(2)在一段时间内公告可能没有任何更新,那么在这段时间内所有客户端请求的数据都没有实际作用,浪费带宽。所以服务端在收到请求时需要根据最新公告的时间戳来判断这段时间内公告是否有更新,如果没有,那么告诉客户端(返回信息):“公告没有更新,不需要获取”。

(3)因为有轮询的间隔,只有请求后才能得到最新的公告,所以无法第一时间得到最新结果。当然,目前这个功能要求的实时性不高,但是这就说明了一些实时性比较高的功能(比如聊天)不适合使用这种方式去实现。

(4)客户端很多(比如网页客户端)/轮询的间隔较短,会给服务端带来很大的压力。

总体来说,这种解决方案效率不高,没有实时性,局限性很大。

举个例子:我在第1s成功地强化了我的武器,然而要多等59s,公告才会显示“恭喜玩家xie4ever强化武器成功”。这显然不符合常识。

题外话:方案一是否就没有任何的可取之处呢?

具体情况需要具体分析。我之前做过数据库监控的功能,最初使用的就是轮询的方式:

6

在即时性要求不高、并发量不大的情况下,使用轮询问题不大。

2.socket

在服务端发出一条公告的时候,客户端马上就需要更新公告(个对象的改变需要同时改变其他对象),但是服务端不需要知道当前有多少客户端(不知道具体有多少对象有待改变时)。

其实我们可以把这个过程想象成服务端和客户端“聊天”:更新公告后,服务端通过socket向客户端发送数据(广播),客户端对数据进行渲染就可以了。

可以这样设计结构:

999

如果客户端是网页,那么使用websocket就好了(这也是当前流行的解决方案,听说ajax长轮询也不错(至少比普通轮询要好),这种方式我没接触过)。

这种解决方案似乎有点观察者模式的思想。一旦客户端上线,就保存其socket对象(把客户端放入观察者名单中),一旦服务端更新公告(被监听对象发生了变化),那么马上给所有socket推送最新的公告(观察者采取相应的动作)。

四、观察者模式的缺点

参考:http://www.bianchengzhe.com/DesignPattern/neirong/221.html

优点已经提得很多了:抽象耦合、支持广播通讯。这里主要讨论观察者模式的缺点。

1.存在大量观察者

如果一个被观察者对象有很多的直接/间接的观察者,那么通知所有观察者将会花费很多时间。

问题是,谁遇到这种情况谁都很绝望啊。严格来说不是观察者模式的锅。

2.可能出现循环

如果在观察者之间存在循环依赖,被观察者会触发循环调用导致崩溃。

就像tcp建立握手时的问题:“我发了一个包给你”,“我收到你的包了”,“我收到你收到我的包了的信息了”…如果一方不及时收手,就会造成循环。

3.异步投递

如果通过异步线程对观察者进行通知(异步调用观察者的方法?),必须保证自恰。

我不知道怎么理解这个概念…

4.无法知道发生了什么变化

观察者随时可以知道被观察者发生了变化,但是不知道被观察者是怎么变化的。

被观察者不是万能的,问题是:观察者是否有必要知道被观察者是怎么变化的,发生了什么变化?个人认为不需要考虑得这么复杂。

五、总结

重看一下聊天室的实现,也许能更好地理解观察者模式。

发表评论

电子邮件地址不会被公开。 必填项已用*标注