观察者模式
在观察者模式
中,多个观察者
监听同一个目标对象。当目标对象的状态发生改变的时候,通知所有观察者
,进行更新操作。 它们之间是直接进行通信的,由发布者
直接通知观察者
进行更新。
发布/订阅模式
消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。
而是将发布的消息分为不同的类别(消息过滤),无需了解哪些订阅者(如果有的话)可能存在。
同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
——维基百科
发布/订阅模式
和观察者模式
最大的区别就是,发布者
和订阅者
并不会直接进行通信,而是交给一个消息中间件——消息代理(message broker)。
订阅者
只需要告诉消息代理
,它需要接受哪些信息。发布者
只需要告诉消息代理
,它发送了什么信息,然后消息代理
将消息过滤,发送给订阅者
。双方并不关心对方是否存在。
消息过滤
那么消息代理是怎么进行过滤的呢?其实有两种方式:
- 基于主题进行过滤
- 基于内容进行过滤
基于主题
发布者
负责对消息进行分类,发布到主题或信息通道上。订阅了同一主题的所有订阅者
,都将接收到主题里的所有内容。
基于内容
订阅者
负责对消息进行分类,只有消息的属性或内容满足订阅者
定义的条件时,才会投递到该订阅者
。
有些系统中支持两种混合:发布者发布内容到主题,订阅者基于内容的订阅注册到一个或多个主题。
两者对比
- 在
观察者模式
里是松耦合的,而在发布/订阅模式
里是解耦的。 观察者模式
里大多是同步操作,而发布/订阅模式
大多是异步操作。- 在
观察者模式
里Observer是知道Publisher的,Publisher也一直保持对Observer的记录。而发布/订阅模式
里,它们并不知道对方的存在。并且只通过消息代理
进行通信。 观察者模
式大多是存在应用程序内部,而发布/订阅模式
存在于两个应用程序之间。
例子——Event Bus
在Vue
中如果在不使用Vuex
的情况下,实现两个组件通信,可以使用Event Bus
。它的作用就是作为一个全局事件总线,来实现两个相隔千里的组件相互通信。其实现的原理就是使用发布/订阅模式
。让全局事件总线
来充当消息代理
。
实现一个Event Bus
EventBus.js
// 本质上EventBus就是一个Vue实例
const bus = new Vue();
export default bus;
main.js
import bus from 'EventBus所在的路径';
// 注册到Vue原型上
Vue.prototype.bus = bus;
AComponent.js
// 监听"sayHi"事件,第二参数是监听到该事件时的执行回调函数
this.bus.$on('sayHi', callback);
BComponent.js
// 派发"sayHi"事件,第二个参数是要传进"回调函数"的参数
this.bus.$emit('sayHi', params);
来实现一个EventBus
class EventBus {
constructor() {
// 使用Map来存储每一个EventName对应的所有回调函数
this.handlers = new Map();
}
// 监听目标事件,接收事件名和回调函数
on(eventName, callback) {
const { handlers } = this;
// 如果EventName对应的回调函数栈存在,则继续往里面存新的回调函数
if (handlers.get(EventName)) {
handlers.get(EventName).push(callback);
} else {
// 否则创建新的回调函数栈
handlers.set(EventName, [callback]);
}
}
// 触发目标事件,传入事件名和参数
emit(eventName, ...args) {
const { handlers } = this;
// 先查询该方法名是否存在
if (handlers.get(eventName)) {
// 存在则调用所有订阅者的回调函数
handlers.get(eventName).forEach(callback => callback(...args));
} else {
// 不存在就抛出异常
throw new Reference('Methods not exits');
}
}
// 移除某个事件里的指定回调函数
off(eventName, callback) {
const { handlers } = this;
const index = handlers.get(eventName).indexOf(callback);
if (index > -1) {
handlers.get(eventName).splice(index, 1);
} else {
throw new Reference('Methods not exits');
}
}
// 注册单次的监听器
once(eventName, callback) {
const wrapper = (...args) => {
callback(..args);
this.off(eventName, callback);
}
this.on(eventName, wrapper);
}
}
参考
https://zh.wikipedia.org/wiki/%E5%8F%91%E5%B8%83/%E8%AE%A2%E9%98%85
https://zh.wikipedia.org/wiki/%E6%B6%88%E6%81%AF%E4%BB%A3%E7%90%86