你有没有遇到过这样的场景:当一个数据变化时,多个地方都需要跟着更新?比如用户修改了头像,个人资料页、评论区、聊天窗口都要同步显示新头像。这时候,设计模式中的「观察者模式」和「发布订阅模式」就派上用场了。今天带你彻底搞懂这对「孪生兄弟」!
一、观察者模式:老师点名,学生答到
观察者模式的核心思想就是「你盯着我,我一动你就跟着动」。它定义了对象间的一对多依赖关系,当被观察者状态变化时,会自动通知所有观察者。
生活例子:老师点名
最形象的类比就是老师点名。老师(被观察者)念到学生名字时,学生(观察者)必须立刻答到。老师直接认识每一个学生,学生也知道自己要对老师的点名做出反应。
代码实战:DOM属性监听
我们用JavaScript实现一个简单的观察者模式:
// HTML结构
// <h2>1</h2>
// <button>点击</button>
let btn = document.querySelector('button');
let h2 = document.querySelector('h2');
let obj = { count: 1 };
let num = obj.count; // 防止递归爆栈
function observer(value) {
// 找到订阅者(h2),更新内容
h2.innerHTML = value;
}
Object.defineProperty(obj, 'count', {
get() { return num; },
set(value) {
observer(value); // 通知观察者
num = value;
}
});
btn.addEventListener('click', () => { // 每点击一下, h2标签的值+1
obj.count++;
console.log('当前count:', obj.count);
});
在这个例子中,我们通过Object.defineProperty监听对象属性的变化。当obj.count被修改时,会自动调用observer函数更新DOM。这就是观察者模式的核心:被观察者状态变化时,直接通知观察者。
优点
- 实现简单,容易理解
- 响应迅速,被观察者变化立即通知观察者
- 适合小范围、强关联的场景
二、发布订阅模式:小区公告栏,物业贴通知
发布订阅模式则是「你别盯着我,咱们都找小黑板(事件中心)留言」。它通过一个事件中心来解耦发布者和订阅者,三者互不认识。
生活例子:小区公告栏
物业(发布者)把通知贴在公告栏(事件中心),业主(订阅者)自己去公告栏看通知。物业不需要知道哪些业主会看通知,业主也不需要知道通知是谁发的。大家只和公告栏打交道。
代码实战:EventEmitter实现
我们来实现一个简单的事件中心:
class EventEmitter {
constructor() {
this.eventList = {};
}
on(eventName, callback) {
if (!this.eventList[eventName]) {
this.eventList[eventName] = [];
}
this.eventList[eventName].push(callback);
}
off(eventName, callback) {
const callbacks = this.eventList[eventName];
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
}
once(eventName, callback) {
const wrap = () => {
callback();
this.off(eventName, wrap);
};
this.on(eventName, wrap);
}
emit(eventName) {
if (this.eventList[eventName]) {
const handlers = this.eventList[eventName].slice();
handlers.forEach(item => item());
}
}
}
// 使用示例
function hu() { console.log('胡买房'); }
function lai() { console.log('赖买房'); }
function zhong() { console.log('钟买车位'); }
let _event = new EventEmitter();
_event.on('hasHouse', hu);
_event.once('hasHouse', lai);
_event.on('hasCarport', zhong);
_event.emit('hasHouse'); // 胡和赖都买房
_event.emit('hasHouse'); // 赖只买一次, 只有胡继续买房
_event.emit('hasCarport'); // 钟买车
在这个例子中,EventEmitter就是事件中心。发布者调用emit方法发布事件,订阅者通过on方法订阅事件。发布者和订阅者之间没有直接联系,完全通过事件中心进行通信。
优点
- 解耦性强,发布者和订阅者互不依赖
- 灵活性高,可以轻松添加或移除订阅者
- 适合复杂、跨模块的通信场景
三、核心对比:一张表看明白
| 特性 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 耦合度 | 紧耦合 | 松耦合 |
| 通信方式 | 直接通知 | 事件中心转发 |
| 角色数量 | 两个(被观察者、观察者) | 三个(发布者、订阅者、事件中心) |
| 适用场景 | UI响应、数据绑定 | 跨模块、全局事件管理 |
| 代码复杂度 | 简单 | 略高 |
四、应用场景举例
观察者模式的应用
- Vue响应式系统:当数据变化时,视图自动更新
- Excel单元格联动:一个单元格的值变化,关联的单元格自动更新
- 游戏中的成就系统:当玩家达到条件时,立即解锁成就
发布订阅模式的应用
- Node.js EventEmitter:处理异步事件
- 浏览器自定义事件:不同组件间通信
- 消息队列:如RabbitMQ、Kafka等,实现系统间解耦通信
- 微信公众号:作者发布文章,订阅的用户收到通知
五、总结
观察者模式就像「班主任和学生」,老师一喊,学生立刻响应;发布订阅模式就像「微信群公告」,群主发消息,谁看谁响应,群主不用知道谁看了。
选择哪种模式,取决于你的具体需求:如果是小范围、强关联的场景,观察者模式简单直接;如果是复杂、跨模块的通信,发布订阅模式更灵活。