zhangdizhangdi

观察者 & 发布订阅模式 🔥

定义

观察者模式(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)
通信方向 目标 → 观察者 发布者 → 调度中心 → 订阅者
适用场景 状态同步、单组件内的事件驱动 跨组件、跨模块、甚至跨进程的异步消息驱动

与设计原则的关系

这两种模式完美契合了面向对象设计中的多个核心原则:

  1. 开闭原则 (Open-Closed Principle)
    • 你可以随时增加新的观察者或订阅者,而不需要修改目标对象(Subject)或发布者的代码,系统扩展性极强。
  2. 单一职责原则 (Single Responsibility Principle)
    • 目标对象只负责维护自己的状态和触发通知;观察者只负责处理自己的更新逻辑。职责分明。
  3. 依赖倒置原则 (Dependency Inversion Principle)
    • (特别是观察者模式中)目标对象依赖的是观察者的抽象接口(如都有 update 方法),而不是具体的实现类。
  4. 迪米特法则 / 最少知识原则 (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(订阅者)重新渲染组件。
  • Node.js 中的 EventEmitter
    • Node.js 核心模块 events 就是纯粹的发布订阅模式,贯穿了 Stream、HTTP 等整个 Node.js 生态。
  • Web Worker / Iframe 通信
    • 使用 postMessageonmessage 进行跨线程/跨域的消息发布与订阅。
  • WebSocket / MQTT
    • 前端实时通信、物联网应用中,客户端往往向服务器订阅特定的 Topic 频道,服务器推送消息时触发前端回调。