一、观察者模式:从“电话”到设计
1. 观察者模式的定义与核心结构
观察者模式(Observer Pattern)是一种行为型设计模式,定义了一种一对多的依赖关系。当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)会自动收到通知并更新。
是不是感觉和上一篇中的发布订阅模式很像?你先别急,慢慢往下看
2. 气象台的电话通知
假设你住在山区,每天通过电话向气象台“订阅”天气预报。气象台每天固定时间打电话通知你:“明天有暴雨,请注意安全!”
- 气象台(被观察者):维护所有订阅电话的列表,当天气变化时主动拨打电话。
- 你(观察者):收到电话后执行具体操作(如准备雨具、转移物资)。
这种 “直接通知” 的机制,是观察者模式的核心思想。
核心角色
-
被观察者(Subject)
- 维护观察者列表,提供添加/移除观察者的方法。
- 在状态变化时主动调用观察者的更新方法。
-
观察者(Observer)
- 定义统一的更新接口(如
update()方法)。 - 当收到通知时,执行具体操作。
- 定义统一的更新接口(如
3. 观察者模式的代码实现
以“气象台通知用户”为例:
// 被观察者:气象台
class WeatherStation {
constructor() {
this.observers = []; // 保存所有订阅者
this.temperature = 0; // 当前温度
}
// 添加观察者(用户)
addObserver(observer) {
this.observers.push(observer);
}
// 移除观察者
removeObserver(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
// 设置温度并通知所有观察者
setTemperature(temp) {
this.temperature = temp;
this.notifyObservers();
}
// 通知所有观察者
notifyObservers() {
this.observers.forEach(observer => {
observer.update(this.temperature);
});
}
}
// 观察者:用户
class User {
constructor(name) {
this.name = name;
}
update(temp) {
console.log(`${this.name} 收到通知:当前温度为 ${temp}℃`);
}
}
// 使用示例
const station = new WeatherStation();
const userA = new User("张三");
const userB = new User("李四");
station.addObserver(userA);
station.addObserver(userB);
station.setTemperature(30); // 气象台更新温度,通知所有用户
输出结果:
张三 收到通知:当前温度为 30℃
李四 收到通知:当前温度为 30℃
二、发布订阅模式:从“群聊”到设计
1. 生活场景引入:微信群聊通知
你加入了一个“暴雨预警”微信群,群里有气象局、应急办等多个成员。当气象局发布“暴雨预警”消息时,群里的所有成员(包括你)都会收到通知。
- 微信群(事件中心):管理消息的发布与订阅。
- 气象局(发布者):只负责发布消息,不关心谁会收到。
- 你(订阅者):只接收感兴趣的消息,不关心消息来源。
这种 “间接通知” 的机制,是发布订阅模式的核心思想。
2. 发布订阅模式的代码实现
通过事件中心(Event Bus)解耦发布者和订阅者:
class EventBus {
constructor() {
this.events = {};
}
on(eventName, callback) {
if (!this.events[eventName]) this.events[eventName] = [];
this.events[eventName].push(callback);
}
emit(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(data));
}
}
}
三、观察者模式 vs 发布订阅模式:全方位对比
| 维度 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 通信方式 | 被观察者直接调用观察者的 update() 方法,通信是同步的。 | 事件中心异步分发消息,订阅者通过回调函数接收消息。 |
| 耦合度 | 高耦合:被观察者直接持有观察者的引用,两者需要知道对方的存在。 | 低耦合:发布者和订阅者通过事件中心通信,彼此无需知道对方的具体实现。 |
| 依赖关系 | 被观察者和观察者之间存在直接依赖关系。 | 发布者和订阅者通过事件中心间接通信,无直接依赖。 |
| 灵活性 | 被观察者需要维护观察者列表,扩展性较低。 | 事件中心独立于发布者和订阅者,扩展性强,可动态添加/移除订阅者。 |
| 适用场景 | 适用于强耦合的场景,如 UI 更新、响应式数据绑定(Vue/React)。 | 适用于松耦合的场景,如微服务通信、消息队列(Kafka/RabbitMQ)、跨层级组件通信。 |
| 典型应用 | - Vue 的响应式系统(Vue 2) - DOM 事件模型(如 addEventListener) | - Redux/Vuex 状态管理 - Node.js 的 EventEmitter- 微服务架构中的事件驱动 |
四、Vue 响应式:观察者模式的典型应用(拓展)
1. Vue 2 的响应式原理
Vue 2 通过 Object.defineProperty 实现响应式数据:
- 数据劫持:遍历
data对象的所有属性,设置get和set拦截读写。 - 依赖收集:在
get中收集Watcher(观察者),建立数据与视图的依赖关系。 - 派发更新:在
set中触发依赖更新,通知Watcher重新渲染视图。
1.1 什么是 Object.defineProperty?
Object.defineProperty 是 JavaScript 原生方法,用于直接在对象上定义新属性或修改现有属性,并返回该对象。通过设置 get 和 set 方法,可以拦截属性的读写操作,实现数据劫持。
语法
Object.defineProperty(obj, prop, descriptor);
-
参数:
obj:要在其上定义属性的对象。prop:要定义或修改的属性的名称。descriptor:属性描述符,包含value、writable、enumerable、configurable、get、set等特性。
关键特性
- 数据描述符:定义属性的值(
value)和可写性(writable)。 - 存取描述符:通过
get和set拦截属性的读写操作。
1.2 Object.defineProperty 的代码实现
const obj = {};
let internalValue = 0;
Object.defineProperty(obj, 'count', {
get() {
console.log('读取 count 属性');
return internalValue;
},
set(newVal) {
console.log('修改 count 属性为', newVal);
internalValue = newVal;
},
enumerable: true,
configurable: true
});
obj.count = 10; // 修改 count 属性为 10
console.log(obj.count); // 读取 count 属性 → 输出 10
劫持属性后使用get或set方法进行数据更新和通知发布的的操作即可实现响应式更新视图
本文此处知识点目前作为拓展介绍,具体源码分析不是本文侧重点。故不附源码实现
2. 局限性
- 无法监听数组索引变化(需重写数组方法)。
- 无法检测属性的添加/删除(需手动调用
Vue.set)。 - 需递归遍历对象,性能较差。
五、总结
- 观察者模式:像电话群,直接通知,适合强耦合场景(如 Vue 响应式)。
- 发布订阅模式:像微信群,间接通信,适合松耦合场景(如微服务通信)。
通过合理选择这两种模式,可以显著提升代码的可维护性和扩展性。理解它们的区别,能让你在设计系统时灵活高效! 🌟