在日常开发中,我们经常会听到"发布-订阅模式"和"观察者模式"这两个概念。
它们看起来非常相似,甚至很多人以为是同一个东西。
但其实------它们思想相近,结构不同,耦合度差别巨大。
一、两者到底有什么区别?
我们先用一句话总结👇
观察者模式: 被观察者直接通知观察者。
发布-订阅模式: 发布者和订阅者通过中间事件中心通信。
来看下对比表:
项目 | 观察者模式(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
。
所有常见的事件系统(window
、document
、HTMLElement
)都继承自它。
javascript
const btn = document.querySelector('button');
// 订阅
btn.addEventListener('click', (e) => {
console.log('按钮被点击了', e);
});
// 发布
btn.dispatchEvent(new Event('click'));
五、总结
模式 | 通信关系 | 是否解耦 | 常见场景 |
---|---|---|---|
观察者模式 | 被观察者 ↔ 观察者 | ❌ 否 | Vue2 响应式系统 |
发布-订阅模式 | 发布者 → 事件中心 → 订阅者 | ✅ 是 | Node.js EventEmitter、浏览器事件、消息系统 |
EventTarget | 浏览器内置的发布订阅实现 | ✅ 是 | DOM 事件系统 |