发布-订阅(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 事件系统
相关推荐
Aniugel1 小时前
单点登录(SSO)系统
前端
鹏多多1 小时前
移动端H5项目,还需要react-fastclick解决300ms点击延迟吗?
前端·javascript·react.js
serioyaoyao1 小时前
上万级文件一起可视化,怎么办?答案是基于 ParaView 的远程可视化
前端
万少1 小时前
端云一体 一天开发的元服务-奇趣故事匣经验分享
前端·ai编程·harmonyos
WindrunnerMax1 小时前
从零实现富文本编辑器#11-Immutable状态维护与增量渲染
前端·架构·前端框架
不想秃头的程序员1 小时前
Vue3 封装 Axios 实战:从基础到生产级,新手也能秒上手
前端·javascript·面试
数研小生1 小时前
亚马逊商品列表API详解
前端·数据库·python·pandas
你听得到111 小时前
我彻底搞懂了 SSE,原来流式响应效果还能这么玩的?(附 JS/Dart 双端实战)
前端·面试·github
不倒翁玩偶1 小时前
npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
前端·npm·node.js