Observer Design Pattern
GoF 给出的定义: Define a one to many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
当一个对象状态变化时(被观察者),多个依赖的对象收到消息(观察者)响应变化。
怎么理解?
假设你喜欢炒股,需要及时关注一只股票的价格变化,刚开始你有空就手动查询一次,时间一长,感觉太麻烦,而且经常错过价格的波动,错失机会,怎么办?
如果能搞个监控,在股票价格符合阈值时,发个通知就好了。此时你就是观察者,股票是被观察者。
非观察者模式实现
在需求不复杂时,可以简单实现监控股票价格功能,写一个定时脚本,每分钟执行一次,脚本逻辑:
- 获取股票价格
- 价格满足阈值
- 发送通知
一段时间后,你的朋友听说你有这个好东西(需求变复杂),想让你帮忙监控几只股票。此时发生了 2 个变化:
- 被观察者变多了(更多的股票)
- 观察者变多了(增加了你的朋友)
上面的脚本要匹配新的需求,可能会修改成:
- 获取 A 股票价格
- 如果满足阈值 1,通知我
- 如果满足阈值 2,通知我朋友
- 获取 B 股票价格
- 如果满足阈值 3,通知我朋友
- 获取 C 股票价格
- ...
代码已经开始出现坏味道了...
又过了一段时间,更多的朋友知道了这件事,找你监控股票,改着改着,你崩溃了。
观察者模式实现
如何解决上面的困境?
问题的本质是不断增加的观察者和被观察者,如果只有一个观察者和被观察者就没有这么复杂,脚本简单写下就能搞定,如果不断增加,代码就会越来越复杂,最终难以维护。在业务上看,观察者的增加会比较频繁,被观察者可以看成独立的,因此问题可以简化为 GoF 所说的一对多关系。
我们把观察者和被观察者抽象出来:
- 观察者的行为是在状态变化时收到通知
- 被观察者的行为是在状态变化时发送通知
并且要建立两者之间的依赖关系,需要把观察者注册到被观察者的接口和观察者移除的接口。
由此可以得到:UML Class and Sequence diagram

里面的 Subject(主题)就是被观察者。
interface Subject {
func attach(Observer)
func detach(Observer)
func notify()
}
interface Observer {
func update()
}
状态如何通知
这里有一个 trick 的点,就是状态变更后,当前的状态如何通知给 Observer。例如股票价格变化后,价格如何通知观察者。
第一种方式是 push 过去,在 update() 方法上增加状态参数,直接传入。这种方法的弊端是如果状态的定义变化,需要获取更多信息而修改函数签名,可能会导致一系列问题。
第二种方法是 pull 主动拉,这样需要在 update() 获取当前的 Subject,变成 update(Subject)
,并在 Subject 提供一个 getState 查询状态的方法。
我倾向于在变化少的场景使用 push 方式,在经常变化的场景使用 pull 的方式(其实也可以用 push,定义一个消息结构,方便添加字段),通过接口获取状态,更解耦。当然也可以两者结合起来使用。
Java 标准库中的观察者模式
在 Java 标准库中观察者模式应用及其广泛,Java Util 包中还提供了 java.util.Observable
(Subject)和 java.util.Observer
接口以方便开发人员在编程过程中快速使用观察者模式。
package java.util
public interface Observer {
void update(Observable o, Object arg);
}
这种方式把 push 和 pull 结合起来了,第二个参数 Object 用于传递变化的数据。
package java.util
public interface Observable {
void addOberver(Observer o)
void deleteOberver(Observer o)
protected synchronized setChanged() { changed = true }
protected synchronized clearChanged() { changed = false }
void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed) return;
arrLocal = obs.toArray() // 拷贝一份观察者进行通知
clearChanged()
}
for (int i = arrLocal.length - 1; i>=0; i--) {
((Observer)arrLocal[i]).update(this, arg);
}
}
}
setChanged 和 clearChanged 设置状态变化情况,在调用 notifyObservers 方法之前一定要调用 setChanged 方法,设置状态发生了改变,否则不会触发通知。
其它实现
Zustand 的核心原理也是该模式,一堆组件观察状态,状态变化时挨个通知组件更新。
为什么需要这个模式?
- 更好地管理需求变化,符合 OCP,新增观察者与被观察者,不需要修改原有代码,只要扩展新的类
- 更好地解耦,管理依赖,对观察者来说,只需要关心状态变化时得到的通知
- 是好莱坞原则的一个应用
资料
- 《漫谈设计模式》
- 【「观察者模式」与「发布/订阅模式」,你分得清楚吗?-哔哩哔哩】 https://b23.tv/oiMPYJ8