发布-订阅(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 事件系统
相关推荐
AlienZHOU4 小时前
DeepSeek Harness 插件:HTML 实时可视化编辑
前端·agent·deepseek
剪刀石头布啊7 小时前
javascript手动实现继承
前端
Elias不吃糖7 小时前
Langfuse 入门:Trace、Prompt、Dataset、Experiment、Evaluator
前端·python·prompt·langfuse
vipbic7 小时前
一个前端的 9 天重构:我是怎么用 Codex 重做航栈的
前端·javascript·后端
Eason_Lou9 小时前
【无标题】
前端·vue.js·html
_codemonster9 小时前
npm run dev 是在开发模式运行,怎么在生产环境运行
前端·npm·node.js
kyriewen10 小时前
我受够了复制报错去问 AI,花一下午给控制台做了个调试助手
前端·javascript·ai编程
IT_陈寒11 小时前
Redis的DEL命令竟然没删掉数据?我踩的这个坑你得知道
前端·人工智能·后端
anOnion11 小时前
构建无障碍组件之Menu and Menubar Pattern
前端·html·交互设计