发布-订阅(Publish–Subscribe) vs 观察者模式(Observer Pattern)

在日常开发中,我们经常会听到"发布-订阅模式"和"观察者模式"这两个概念。

它们看起来非常相似,甚至很多人以为是同一个东西。

但其实------它们思想相近,结构不同,耦合度差别巨大

一、两者到底有什么区别?

我们先用一句话总结👇

观察者模式: 被观察者直接通知观察者。
发布-订阅模式: 发布者和订阅者通过中间事件中心通信。

来看下对比表:

项目 观察者模式(Observer Pattern) 发布-订阅模式(Publish--Subscribe Pattern)
核心思想 对象直接观察另一个对象的状态变化 借助第三方"事件通道"(事件总线 / 消息代理)
参与者 被观察者(Subject)+ 观察者(Observer) 发布者(Publisher)+ 订阅者(Subscriber)+ 中间件(Broker/EventBus)
通信方式 观察者直接注册到被观察者上 双方通过事件中心间接通信
耦合度 高(Subject 必须知道 Observers) 低(Publisher 不知道 Subscriber)
典型场景 Vue2 响应式系统、数据绑定 Node.js EventEmitter、DOM事件系统、微前端通信

二、观察者模式(Observer Pattern)

被观察者直接持有观察者引用,状态变化时主动通知。

思想图

flowchart TD A[Subject] --> B[Observer 1] A --> C[Observer 2] A --> D[Observer 3]

代码示例

javascript 复制代码
// 被观察者
class Subject {
  constructor() {
    this.observers = [];
  }

  // 添加观察者
  attach(observer) {
    this.observers.push(observer);
  }

  // 移除观察者
  detach(observer) {
    this.observers = this.observers.filter(o => o !== observer);
  }

  // 通知所有观察者
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

// 观察者
class Observer {
  constructor(name) {
    this.name = name;
  }

  update(data) {
    console.log(`${this.name} 收到通知:`, data);
  }
}

// 使用
const subject = new Subject();
const obs1 = new Observer('观察者A');
const obs2 = new Observer('观察者B');

subject.attach(obs1);
subject.attach(obs2);

subject.notify('主题发生变化');
// 输出:
// 观察者A 收到通知: 主题发生变化
// 观察者B 收到通知: 主题发生变化

特点总结

  • 观察者必须主动注册到被观察者;
  • 被观察者维护观察者列表;
  • 二者直接绑定,耦合较高;
  • 常用于对象间状态同步。

三、发布-订阅模式(Publish--Subscribe Pattern)

借助一个"事件总线(EventBus)"作为中介,发布者和订阅者完全解耦。

🧠 思想图

css 复制代码
flowchart TD
  subgraph Bus[事件总线]
    X[EventBus]
  end
  A[Publisher] --> X
  X --> B[Subscriber 1]
  X --> C[Subscriber 2]
  X --> D[Subscriber 3]

代码示例

kotlin 复制代码
class EventBus {
  constructor() {
    this.events = {};
  }

  // 订阅事件
  subscribe(event, callback) {
    if (!this.events[event]) this.events[event] = [];
    this.events[event].push(callback);
  }

  // 取消订阅
  unsubscribe(event, callback) {
    if (!this.events[event]) return;
    this.events[event] = this.events[event].filter(cb => cb !== callback);
  }

  // 发布事件
  publish(event, data) {
    if (!this.events[event]) return;
    this.events[event].forEach(cb => cb(data));
  }
}

// 使用
const bus = new EventBus();

bus.subscribe('orderCreated', (data) => {
  console.log('📦 订单模块收到新订单:', data);
});

bus.subscribe('orderCreated', (data) => {
  console.log('📊 数据模块统计新订单:', data);
});

// 发布事件
bus.publish('orderCreated', { id: 101, item: '卡丁车' });

✅ 特点总结

  • 发布者与订阅者完全解耦;
  • 可以存在多个独立事件通道;
  • 更适合模块化系统或跨组件通信;
  • 可扩展性更强。

四、浏览器中的 EventTarget:内置的发布订阅系统

浏览器其实自带一个发布订阅机制------EventTarget

所有常见的事件系统(windowdocumentHTMLElement)都继承自它。

javascript 复制代码
const btn = document.querySelector('button');

// 订阅
btn.addEventListener('click', (e) => {
  console.log('按钮被点击了', e);
});

// 发布
btn.dispatchEvent(new Event('click'));

五、总结

模式 通信关系 是否解耦 常见场景
观察者模式 被观察者 ↔ 观察者 ❌ 否 Vue2 响应式系统
发布-订阅模式 发布者 → 事件中心 → 订阅者 ✅ 是 Node.js EventEmitter、浏览器事件、消息系统
EventTarget 浏览器内置的发布订阅实现 ✅ 是 DOM 事件系统
相关推荐
Hilaku5 小时前
技术Leader的“第一性原理”:我是如何做技术决策的?
前端·javascript·面试
云中雾丽5 小时前
Flutter 里的 Riverpod 用法解析
前端
前端snow5 小时前
记录:非常典型的一个redux问题
前端
慧一居士5 小时前
src/App.vue 和 public/index.html 关系和区别
前端·vue.js
九十一6 小时前
websocket的连接原理
前端·javascript
念你那丝微笑6 小时前
vue实现批量导出二维码到PDF(支持分页生成 PDF)
前端·vue.js·pdf
Renounce6 小时前
《Android Handler:线程间通信的核心实现》
前端
CAD老兵6 小时前
打造高性能二维图纸渲染引擎系列(一):Batched Geometry 助你轻松渲染百万实体
前端·webgl·three.js
前端老宋Running6 小时前
微信小程序的操作日志收集模块
前端