观察者 & 发布订阅模式 🔥
定义
观察者模式(Observer Pattern) 与 发布订阅模式(Publish-Subscribe Pattern) 是软件开发中极为常用的行为型设计模式,它们主要用于解决对象之间的通信与状态同步问题。虽然两者经常被混为一谈,但它们在结构和耦合度上存在显著差异。
观察者模式 (Observer Pattern)
定义对象间的一种一对多的依赖关系,当一个对象(Subject,目标/主题)的状态发生改变时,所有依赖于它的对象(Observer,观察者)都会得到通知并被自动更新。
- Subject(目标/主题):维护一个观察者列表,提供添加、删除和通知观察者的方法。
- Observer(观察者):提供一个更新接口(如
update方法),用于接收 Subject 的状态变化通知。
特点:直接通信。目标和观察者相互知道对方的存在(强耦合或松耦合,取决于是否面向接口编程)。
发布订阅模式 (Publish-Subscribe Pattern)
它是观察者模式的一种变体。发布者(Publisher)不直接将消息发送给订阅者(Subscriber),而是通过一个**第三方组件(事件调度中心/Event Bus/Broker)**来统一管理消息的流转。
- Publisher(发布者):负责产生事件/数据,并将其发送给调度中心。
- Event Channel(事件通道/调度中心):负责接收发布者的消息,并根据主题(Topic)分发给对应的订阅者。
- Subscriber(订阅者):向调度中心订阅自己感兴趣的主题。
特点:间接通信。发布者和订阅者完全解耦,它们甚至不知道彼此的存在。
两者对比
| 维度 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 耦合度 | 较低(主体与观察者存在依赖关系) | 极低(发布者和订阅者完全解耦) |
| 组件数量 | 2个(Subject, Observer) | 3个(Publisher, Event Channel, Subscriber) |
| 通信方向 | 目标 → 观察者 | 发布者 → 调度中心 → 订阅者 |
| 适用场景 | 状态同步、单组件内的事件驱动 | 跨组件、跨模块、甚至跨进程的异步消息驱动 |
与设计原则的关系
这两种模式完美契合了面向对象设计中的多个核心原则:
- 开闭原则 (Open-Closed Principle):
- 你可以随时增加新的观察者或订阅者,而不需要修改目标对象(Subject)或发布者的代码,系统扩展性极强。
- 单一职责原则 (Single Responsibility Principle):
- 目标对象只负责维护自己的状态和触发通知;观察者只负责处理自己的更新逻辑。职责分明。
- 依赖倒置原则 (Dependency Inversion Principle):
- (特别是观察者模式中)目标对象依赖的是观察者的抽象接口(如都有
update方法),而不是具体的实现类。
- (特别是观察者模式中)目标对象依赖的是观察者的抽象接口(如都有
- 迪米特法则 / 最少知识原则 (Law of Demeter):
- 发布订阅模式将这一原则发挥到极致:发布者和订阅者互不相识,它们只与“调度中心”通信。
实现
观察者模式 —— “气象站与显示屏”
气象站(Subject)收集温度,各种电子看板(Observer)根据温度做出不同显示。
javascript
// 1. 目标(Subject)
class WeatherStation {
constructor() {
this.observers =[]; // 维护观察者列表
this.temperature = 0;
}
// 添加观察者
addObserver(observer) {
this.observers.push(observer);
}
// 移除观察者
removeObserver(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
// 状态改变,通知所有观察者
setTemperature(temp) {
this.temperature = temp;
this.notifyObservers();
}
notifyObservers() {
this.observers.forEach(observer => observer.update(this.temperature));
}
}
// 2. 观察者(Observer)
class DisplayBoard {
constructor(name) {
this.name = name;
}
update(temperature) {
console.log(`${this.name} 显示最新温度: ${temperature}℃`);
}
}
// 使用:
const station = new WeatherStation();
const board1 = new DisplayBoard('大厅显示屏');
const board2 = new DisplayBoard('手机APP');
station.addObserver(board1);
station.addObserver(board2);
station.setTemperature(25);
// 输出:
// 大厅显示屏 显示最新温度: 25℃
// 手机APP 显示最新温度: 25℃
发布订阅模式 —— “事件总线 (Event Bus)”
前端极其常见的 EventEmitter 实现。
javascript
// 1. 调度中心 (Event Channel / Broker)
class EventBus {
constructor() {
this.events = {}; // 存储事件及其对应的订阅回调
}
// 订阅者:订阅事件 (Subscriber)
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 发布者:发布事件 (Publisher)
emit(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(data));
}
}
// 取消订阅
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
}
}
}
// 使用:
const eventBus = new EventBus();
// 模块A(订阅者)订阅了 'news'
eventBus.on('news', (data) => {
console.log(`模块A接收到新闻: ${data.title}`);
});
// 模块B(发布者)发布了 'news'
// 模块A和模块B互相不知道对方的存在,通过 eventBus 交流
eventBus.emit('news', { title: '2026年世界杯开幕' });
前端应用
观察者模式
- Vue.js 响应式原理 (Vue 2 & Vue 3):
- 经典的数据劫持+观察者模式。
- 在 Vue 2 中,使用
Object.defineProperty劫持属性,Dep充当 Subject(收集依赖),Watcher充当 Observer。当数据变更时,Dep.notify()通知所有的Watcher去更新视图。
- 在 Vue 3 中,使用
Proxy拦截,track收集依赖(effect),trigger触发更新(通知 effect 执行)。
- 浏览器原生的 Observer APIs:
IntersectionObserver:观察元素是否进入可视区域(常用于图片懒加载)。MutationObserver:观察 DOM 树的节点变化。ResizeObserver:观察元素尺寸大小的变化。
发布订阅模式
- DOM 事件监听 (
addEventListener):document.body.addEventListener('click', fn):浏览器内部维护了调度中心,用户点击时(发布),浏览器查找到挂载的事件函数(订阅)并执行。
- Vue / React 中的跨组件通信:
- Vue EventBus:在 Vue 2 中常通过
new Vue()作为全局事件总线($on,$emit);在 Vue 3 中常使用第三方库mitt。 - 状态管理库 (Redux/Zustand):Redux 内部的核心机制之一就是
subscribe。Store 充当调度中心,组件dispatch动作(发布),Store 状态更新后触发所有的 listener(订阅者)重新渲染组件。
- Vue EventBus:在 Vue 2 中常通过
- Node.js 中的 EventEmitter:
- Node.js 核心模块
events就是纯粹的发布订阅模式,贯穿了 Stream、HTTP 等整个 Node.js 生态。
- Node.js 核心模块
- Web Worker / Iframe 通信:
- 使用
postMessage和onmessage进行跨线程/跨域的消息发布与订阅。
- 使用
- WebSocket / MQTT:
- 前端实时通信、物联网应用中,客户端往往向服务器订阅特定的 Topic 频道,服务器推送消息时触发前端回调。