发布-订阅(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 事件系统
相关推荐
于慨12 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz12 小时前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
从前慢丶12 小时前
前端交互规范(Web 端)
前端
CHU72903512 小时前
便捷约玩,沉浸推理:线上剧本杀APP功能版块设计详解
前端·小程序
GISer_Jing12 小时前
Page-agent MCP结构
前端·人工智能
王霸天13 小时前
💥别再抄网上的Scale缩放代码了!50行源码教你写一个永不翻车的大屏适配
前端·vue.js·数据可视化
小领航13 小时前
用 Three.js + Vue 3 打造炫酷的 3D 行政地图可视化组件
前端·github
@大迁世界13 小时前
2026年React大洗牌:React Hooks 将迎来重大升级
前端·javascript·react.js·前端框架·ecmascript
PieroPc13 小时前
一个功能强大的 Web 端标签设计和打印工具,支持服务器端直接打印到局域网打印机。Fastapi + html
前端·html·fastapi
悟空瞎说13 小时前
深入 Vue3 响应式:为什么有的要加.value,有的不用?从设计到源码彻底讲透
前端·vue.js