观察者模式(发布-订阅)

一句话总结

观察者模式就是”你关注我,我有事就通知你”——一个对象状态变了,所有依赖它的对象自动收到更新,不用轮询去问。


一、先搞懂”这是什么模式”

你订阅了一个 YouTube 频道(“科技杂谈”)。

你不需要每天都打开 YouTube 搜”科技杂谈有没有新视频”。

你只是点了”订阅”按钮。

之后科技杂谈每次发新视频,你自动收到通知。

这就是观察者模式:

  • 科技杂谈频道 = 被观察的对象(Subject)

  • 你 = 观察者(Observer)

  • 订阅按钮 = 注册观察者(subscribe / attach)

  • 发新视频 = 状态变更(notify)

  • 你收到通知 = 观察者被更新(update)

如果没有观察者模式,你要怎么做?

每天早上打开 YouTube,搜科技杂谈。

没有新视频 → 明天再来。

永远不知道什么时候更新。

浪费。这就是轮询。

观察者模式让你从”主动去问”变成”等着被通知”。


二、这个模式解决什么问题

场景:一个气象站,三个显示屏。

气象站温度变了。

  • 手机 App 上的温度要更新

  • 商场的电子广告牌要更新

  • 家里的智能音箱要报”今天 30 度”

没有观察者模式的做法:

 
class WeatherStation:
 
    def set_temperature(self, temp):
 
        self.temperature = temp
 
        phone_app.update(temp)        # 手动通知
 
        billboard.update(temp)        # 手动通知
 
        smart_speaker.update(temp)     # 手动通知
 

问题:每增加一个新的显示设备,你就要改 WeatherStation 的代码。加一个”车载显示屏” → 再加一行。显示屏加了 20 个 → WeatherStation 代码膨胀 20 倍。违反开闭原则(对扩展开放,对修改关闭)。

用观察者模式的做法:

WeatherStation 不关心具体有哪些显示设备。它只维护一个”订阅者列表”。温度变了,遍历列表逐个通知就行。

新加显示屏:显示屏自己调用 WeatherStation.subscribe(自己)。WeatherStation 代码一行都不用改。

像什么? 出版社(Subject)和订报纸的人(Observer):出版社不关心具体谁订了报纸。它只维护一份”订阅名单”。新报纸印好了,按名单寄出去。新用户订阅 → 名单加一个名字。出版社的流程不需要改。


三、观察者模式的角色

主体(Subject / Observable)

  • 被观察的对象。状态变了通知所有人

  • 维护一个观察者列表

  • 提供 subscribe / unsubscribe 方法

观察者(Observer)

  • 订阅了主体状态变化的对象

  • 有一个 update 方法(主体通知你时调用的回调)

具体来说:

  • 主体内部有一个”订阅者名单”:observers = []

  • subscribe(observer):把 observer 加进名单。“用户订阅了”

  • unsubscribe(observer):把 observer 从名单移除。“用户取消订阅”

  • notify(data):遍历名单,每个 observer 调用自己的 update(data)。“通知所有订阅者,数据变了”

发布-订阅模式(Pub-Sub)和观察者模式的区别:

维度观察者模式发布-订阅模式
关联方式观察者直接注册在主体上,主体直接通知观察者,两者有直接关联中间多了一个”消息队列”(Broker / Event Bus),发布者把消息扔进队列,订阅者从队列拿消息
耦合度发布者和订阅者直接关联发布者和订阅者完全不知道对方的存在
类比出版社直接给订户寄报纸出版社把报纸放报刊亭,订户去报刊亭拿。出版社不知道谁订了,订户也不知道谁出的

实际中经常混用这两个叫法。很多所谓的”发布-订阅”实现其实是观察者模式。


四、生活中的例子

微信朋友圈通知

你发了一条朋友圈(Subject 状态变了)。微信服务器通知你的好友(Observer)。好友看到”xxx 发了新动态”。

事件监听(DOM addEventListener)

 
button.addEventListener('click', () => { alert('点击') })
 

button = 主体(Subject),click = 事件类型,addEventListener = subscribe,回调函数 = 观察者的 update 方法

Vue 的响应式系统

data 里的某个值变了。Vue 会自动更新所有用到这个值的组件。底层原理就是观察者模式。每个 data 属性被 get 时收集依赖,被 set 时通知所有依赖更新。

消息队列(Redis 发布-订阅)

一个服务发消息到某个 channel。其他服务 subscribe 这个 channel。各服务之间完全解耦。


五、优缺点

优点:

  1. 解耦。发布者不需要知道谁是订阅者,订阅者不需要知道谁发布了消息,两边可以独立变化

  2. 支持广播通信。一个事件,多个接收者

  3. 符合开闭原则。新增订阅者不需要改发布者代码

缺点:

  1. 订阅者收到通知的顺序不确定,依赖遍历顺序

  2. 如果订阅者太多,通知可能很慢(遍历列表逐个通知 O(n))

  3. 如果订阅者和发布者之间有循环依赖,A 更新 → B 收到通知 → B 更新 → A 又收到通知 → 无限循环。要小心处理,避免死循环

  4. 订阅者如果忘了取消订阅,造成内存泄漏。对象已经不用了,但还挂在订阅者列表里,不会被垃圾回收


一句话讲清

延伸提问:“观察者模式是什么?”

“观察者模式定义了一种一对多的依赖关系。一个对象状态变化时,所有依赖它的对象都会自动收到通知。

核心是三个角色:Subject(被观察者)、Observer(观察者)、subscribe(注册)。

比如按钮的 addEventListener 就是观察者模式——按钮是 Subject,点击事件是状态变化,监听函数是 Observer,addEventListener 就是注册观察者。”

延伸提问:“观察者模式和发布-订阅模式有什么区别?”

“观察者模式里观察者直接注册在主体上,主体直接通知观察者。发布-订阅模式中间多了一层消息队列(Broker),发布者和订阅者完全解耦,互相不知道对方存在。发布-订阅更灵活(可以异步、可以过滤消息),但观察者模式更简单直接。“


记忆口诀

观察者模式 = 你订阅了频道,有更新自动通知你。不用轮询去问。

三个角色:Subject(被观察者)、Observer(观察者)、subscribe(注册)。

把”你主动去问”变成了”等着被通知”。

发布-订阅 = 中间加了个消息队列,发布者和订阅者互相不知道是谁。

速记卡(面试闪卡)

Q1:一句话讲清「观察者模式(发布-订阅)」到底是什么?

A:观察者模式让对象状态变了自动通知订阅者,不用轮询去问。

Q2:1. 这是什么模式 —— 订阅即被通知 —— 怎么理解?

A:像订阅 YouTube 频道:点订阅后博主发片你自动收到提醒,不用每天去搜。频道=Subject,你=Observer,订阅键=subscribe,发新片=notify,提醒=update。没它你就只能轮询,纯浪费。英文 Subject / Observer。

Q3:2. 解决什么问题 —— 去轮询保开闭 —— 怎么理解?

A:气象站温度变,手机 App、广告牌、音箱都要更新。没模式得手动挨个调,加个车载屏就改一次代码(违反开闭原则);有了它只维护订阅名单,新设备自己 subscribe,发布者一行不用改。英文 Open-Closed Principle。

Q4:3. 角色与发布-订阅区别 —— 怎么理解?

A:Subject 维护名单,提供 subscribe/unsubscribe/notify;Observer 有 update 回调。发布-订阅多一层消息队列(Broker),发布者把消息扔队列、订阅者自取,双方互不知道存在,更解耦能异步。英文 Pub-Sub / Broker。

Q5:4. 生活例子与优缺点 —— 怎么理解?

A:朋友圈通知、DOM addEventListener、Vue 响应式、Redis 发布订阅都是它。优点:解耦、广播、合开闭原则;缺点:通知顺序不定、订阅者多时遍历慢、循环依赖会死循环、忘 unsubscribe 内存泄漏。英文 Memory Leak。

Q6:核心速记主线有哪些?

  • 核心:Subject 状态变,自动通知所有 Observer

  • 三角色:Subject(被观察者)、Observer(观察者)、subscribe(注册)

  • 价值:去轮询、符合开闭原则、新增订阅者不改发布者

  • 发布-订阅:多一层 Broker,双方完全解耦可异步

  • 缺点:顺序不定、遍历慢、循环依赖死循环、忘退订泄漏

口诀

A:点下订阅等音告,

状态一变自动报;

轮询去掉解耦妙,

发布订阅加管道。

相关链接