本文为看《Head First设计模式》书后整理的技术分享第二篇。
并结合发布订阅模式作比较和总结。
观察者模式
这个模式可以帮你的对象知悉现况,不会错过该对象感兴趣的事。对象甚至在运行时可决定是否要继续被通知。
设计原则
- 为了交互对象之间松耦合设计而努力。
举例
书中以一个例子来展开观察者模式。
👉Internet气象观测站:一个WeatherData对象负责追踪目前的天气状况(温度、湿度、气压)。我们希望建立一个应用,有三种布告板,分别显示目前的状况、气象统计及简单的预报。当WeatherObject对象获得最新的测量数据时,三种布告板必须实时更新。
而且,这是一个可以扩展的气象站,Weather-O-Rama气象站希望公布一组API,好让其他开发人员可以写出自己的气象布告板,并插入此应用中。
气象监测应用的概况
此系统中的三个部分是气象站(获取实际气象数据的物理装置)、WeatherData对象(汇总来自气象站的数据)和布告板(目前状态、气象统计、天气预报等)。
我们看一看刚开始WeatherData初步设想到的:
1 | class WeatherData{ |
由上面这个设计会出现的以下几个问题:
1 | currentConditionsDisplay.update(temp, humidity, pressure); |
这三个方法针对具体实现编程,不针对接口编程,会导致我们以后再增加或删除布告板必须修改WeatherData类。
认识观察者模式
我们先来看观察者模式,然后再回来看如何将此模式应用到气象观察站。
我们看看报纸和杂志的订阅是怎么回事。
- 报社的业务就是出版报纸;
- 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来,只要你是他们的订订户,你就会一直收到报纸。
- 当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。
- 只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸。
其实观察者模式可以理解成 出版者+订阅者=观察者模式。
如果你了解了报纸的订阅,其实就知道观察者模式是怎么回事了,只是名称不太一样:出版者改为“主题”,订阅者改为“观察者”。
观察者模式 定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。
松耦合的威力
当两个对象之间松耦合,他们依然可以互通,但是不太清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口),主题不需要知道观察者的具体类是谁,做了什么或其他任何细节。任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是实现Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们甚至可以用新的观察者代替现用的观察者,主题不会受到任何影响。同样的,也可以随时删除某主题的一些观察者。
有新类型的观察者出现时,主题的代码不需修改,假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题代码。所以要做的就只是在新类里实现观察者接口,之后注册为观察者即可。
🌟第四个设计原则:为了交互对象之间松耦合设计而努力
回过头我们可以重新设计气象站了。
实现气象站代码
1 | //主题接口 |
在WeatherData中实现主题接口
1 | class WeatherData implements Subject{ |
建立布告板
其中一个布告板:
1 | class CurrentConditionDisplay implements Observer,DisplayElement{ |
使用Java中内置的观察者模式
在java api有内置的观察者模式。java.util包内包含最基本的Observer接口和Observable类。
Java内置的观察者模式如何运作
WeatherData现在扩展自Observable类,并继承到一些增加、删除、通知观察者的方法。
如何把对象变成观察者
实现观察者接口(Java.util.Observer),如何调用任何Observable对象的addObserver()方法,不想再当观察者时,调用deleteObserver()方法即可。
重写WeatherData
1 | class WeatherDataTWO extends Observable{ |
可观察者要如何送出通知:
先调用setChanged()方法,标记状态已经改变的事实。(可有更多的弹性,可以控制推送更新的频率)
然后调用两种notifyObservers()方法中的一个
notifyObservers()
(pull)、notifyObservers(Object arg)
(push)
重写布告板
1 | class CurrentConditionsDisplay implements java.util.Observer,DisplayElement{ |
Java内置观察者的局限:
Observable是一个类而不是一个接口,你必须设计一个类来继承它。如果某个类想同时具有Observable类和另一个超类的行为时就完了,java不支持多重继承。所以有时候需要自己实现一整套观察者模式。
总结定义
观察者模式 定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。
观察者模式和发布订阅模式
区别
我之前以为发布订阅模式里的Publisher就是观察者模式里的Subject,而Subscriber就是Observer。Publisher变化时,就主动去通知Subscriber。
**在发布订阅模式里,发布者并不会直接通知订阅者,发布者和订阅者互不感知。
他们通过第三者,也就是经纪人(Broker)来实现调度的。
发布者只需要告诉Broker,我要发消息,注册topic是AAA;订阅者只需告诉Broker,我要订阅topic是AAA的消息;
于是,当Broker收到发布者发来消息,并且topic是AAA时,就会把消息推给订阅了topic是AAA的订阅者。
也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。
发布订阅模式简单代码结构
1 | class PubSub { |
由上面代码可以看出,发布订阅模式统一由调度中心处理,消除了发布者和订阅者之间的依赖。
总结
从表面上看:
- 观察者模式里,只有两个角色 —— 观察者 + 被观察者
- 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经纪人Broker
从更深层次看:
- 观察者和被观察者,是松耦合的关系
- 发布者和订阅者,则完全解耦
从使用层面看:
- 观察者模式,多用于单个应用内部;
- 发布订阅模式,则更多的是一种跨应用的模式,比如我们常用的消息中间件。
应用场景
比如按钮监听、vue的数据双向绑定、kafka消息队列、Redis、zookeeper…