本文主要对观察者模式进行讲解,并使用观察者模式来模拟海姆达尔在发现敌人来袭后通知雷神托尔和洛基的过程。
一、概念
定义
观察者模式也叫作发布-订阅模式,也就是事件监听机制。观察者模式定义了对象之间的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己,并采取相应活动。
主要结构
- 抽象主题角色
被观察者,把所有观察者对象引用保存在一个集合里,对外提供增加删除的接口
- 具体主题角色
将有关状态存入具体观察者对象,在内部状态发生改变时给所有注册过的观察者发送信息
- 抽象观察者角色
为所有具体观察者定义更新接口
- 具体观察者角色
储存与主题状态自恰的状态,实现更新接口,与主题状态协调
UML类图:
特点:松耦合
观察者只知道观察者实现了某个接口,并不需要知道观察者具体类,实现了哪些细节。
任何时候可以增加观察者。主题唯一依赖的东西是储存观察者实现的列表。所以在运行时添加新的观察者也不会对主题造成影响。
有新的观察者加入时,主题代码不需要改变。主题在意的只是发送通知给观察者。
可以独立的使用主题和观察者,因为他们之间是松耦合的。
改变主题或观察者其一并不需要修改另一方,只要遵守接口就可以。
二、海达姆斯与仙宫人民
海姆达尔(Heimdall)是希芙的哥哥。他是能眼视万物和耳听一切的仙宫守护哨兵,他站在彩虹桥比弗罗斯特上,并注意观察任何对仙宫的袭击。他作为仙宫的守卫站立着,保卫这个城市的大门使任何闯入者远离,是奥丁最为信任的仆人之一。
对于仙宫的住民来说Heimdall是他们的可观察者,当海姆达尔观察到危机,会向所有他需要通知的人发送通知。现在我们用观察者模式来模拟海姆达斯发现危机通知仙宫住民这种情况。
下面是一个UML图,实现了海姆达斯与仙宫人民的解耦,去除强耦合关系,同时哨兵接口可以给仙宫所有哨兵使用,观察者们根据需要实现Action接口
定义哨兵接口
1 | /** |
海姆达斯实现哨兵接口
1 | /** |
AsgardManObserver接口,所有的想接收信息的人都要实现
1 | public interface AsgardManObserver { |
Action接口,需要采取行动的人实现
1 | public interface Action { |
两个需要用到的常量
1 | // 灭霸 |
观察者1:雷神托尔
1 | public class Thor implements AsgardManObserver,Action { |
观察者2:洛基
1 | public class Lokey implements AsgardManObserver,Action { |
观察者3:咸鱼
1 | public class SaltedFish implements AsgardManObserver { |
开始模拟:
1 | public class Test { |
结果:
1 | Heimdall:Frost Giants来袭 |
对抗冰霜巨人一战,洛基叛变,海姆达尔不在向其发送通知~
这是一个典型的观察者模式的推模式,海姆达尔一旦得到敌人来袭的消息就会通知他所维系的观察者,海尔达姆与仙宫住民是松耦合的,都可以独立行动,又不用关注各自的细节(所以他也不知道洛基得到消息后会做什么:)),新来了观察者直接维系到list里扩展性强。
拉模式:将整个被观察者对象引用送给观察者,由观察者获取需要的信息。如下,注意notifyObservers方法的修改
1 | /** |
1.推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
2.推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
3.拉模式会使观察者获取所有被观察者信息,同时可能会多次获取才能会得到需要的所有信息
4.但是想象其实,拉模式也是推模式的一种,只不过是推送个引用过去,里面包含了更多的信息
三、jdk对观察者模式的支持
jdk本身提供了对观察者模式的支持,并且支持推、拉两种方案。主要类或接口是java.util.Observable(抽象类)和java.util.Observer(接口)
这里就举个Head First 设计模式的一个例子吧,天气数据和天气显示器。不同的天气显示器会显示不同的信息。
用来储存天气信息的实体类
1 | public class WeatherPojo { |
被观察者:天气数据
1 | public class WeatherData extends Observable { |
观察者:天气显示板
1 | public class CurrentDisplay implements Observer { |
jdk对观察者模式的实现需要被观察者继承Observable,对代码有一定的侵入,例如如果海姆达尔还要继承阿斯加德人这个类,那就需要我们手动来实现观察者模式了。
四、总结
观察者模式是比较常见的设计模式,我们常见的MVC就是标准的观察者模式,如果感兴趣看以google”使用观察者模式实现mvc”。此外向消息队列的发布订阅模式也是使用的观察者模式,而且是异步的性能更好,像我们上面实现的这种简单遍历,如果观察者实现复杂那性能看就会受到影响,毕竟要等待一个观察者执行完才能通知下一个观察者。
本文首发于cdream个人博客
欢迎转载,转载请注明出处!
参考资料: