1. 简介
前面介绍了观察者模式,就好比我们去点餐,通知服务员说,餐好了跟我说一下。那么服务员和顾客之间就形成了耦合,首先服务员得知道餐品好了以后通知那些顾客,其次,如果是多位服务员协作,每个服务员都需要知道这些顾客。
但事实上你发现去 kfc 点餐的时候,服务员并没有直接通知我们。而是采用叫号的方式。细想一下,你去 kfc,是不是可以在点餐系统进行排号(网上或者排队,这里抽象一下),餐品好了以后,服务员输入点餐号,点一下完成即可,点餐系统会通知对应的顾客取餐。
这里你和服务员之间的消息通过点餐系统来传递,你并不需要知道是谁点的完成,服务员也不需要知道这份餐品给谁。完美解耦了消息的发送者和接收者。更好地是,我们在点餐或者叫号的时候其实还可以指定行为,比如说 66 号产品好了以后帮我送到 A1 桌。
再比如说炒股的时候,我们可以委托挂单,就是当股票到了某一个价格就帮你买入或者卖出,等等,例子很多。
2. 用例图
image.png3. 实现
node 中 EventEmitter 就是这样一个典型例子。我们来简单实现一个 EventEmitter。
interface HandlerInfo {
handler: Function;
once?: boolean;
}
class EventEmitter {
private events: Map<string, HandlerInfo[]> = new Map();
on(type: string, handler: Function, once?: boolean) {
if (!this.events.has(type)) {
this.events.set(type, []);
}
(this.events.get(type) || []).push({
handler,
once,
});
return () => {
this.off(type, handler);
};
}
once(type: string, handler: Function) {
return this.on(type, handler, true);
}
emit(type: string, ...args) {
let i = 0;
while (i < (this.events.get(type) || []).length) { // 这里每次都从 this.events 去动态读取,方中途被变更
const handlers: HandlerInfo[] = this.events.get(type) || [];
const { handler, once } = handlers[i];
// 如果是一次性的,应该在调用前删除,防止这里会自己触发自己,导致无限循环或者次序错乱
if (once) {
handlers.splice(i--, 1);
}
i++;
handler(...args); // 这里 this 就交给传入的 handler 来保证了
}
}
off(type?: string, handler?: Function): void {
if (!type) return; // 最好不要默认全部清除,不安全
if (!handler) {
this.events.set(type, []); // 因为这里是直接赋值清空,所以在 emit 的时候,记得每次都从 events 动态获取
return;
}
this.events.set(type, (this.events.get(type) || []).filter(item => item.handler !== handler));
}
}
const eventEmitter = new EventEmitter();
class Person {
public name: string;
constructor(name: string) {
this.name = name;
}
weatherSubscribe(once?: boolean): Function {
return eventEmitter.on('weather', (weather) => {
switch (weather) {
case '雨':
console.log(`${this.name}在家看电影`);
break;
default:
console.log(`${this.name}出去玩`);
}
}, once);
}
weatherNotify(weather) {
eventEmitter.emit('weather', weather);
}
}
const xiaoWang = new Person('小王');
const xiaoMing = new Person('小明');
const xiaoZhang = new Person('小张');
xiaoWang.weatherSubscribe(true);
const off = xiaoMing.weatherSubscribe();
xiaoZhang.weatherNotify('雨');
xiaoZhang.weatherNotify('晴');
off();
xiaoZhang.weatherNotify('晴');
image.png
4. 小结
发布订阅模式可以说是对观察者模式的进一步抽象。
我们通过消息中心对消息进行统一处理,那么这里通知者和消费者的关系其实被弱化了,它们可以是任意对象,通知者和消费者也可以是同一个对象,这种模式甚至在非对象也可以使用,即我们只关注发布和订阅行为本身,而不关心发布订阅者是谁。
参考
从发布订阅模式入手读懂Node.js的EventEmitter源码
使用typescript 写一个简单的事件监听/发布订阅模式的类
TypeScript 设计模式之发布-订阅模式
观察者模式和发布订阅模式的区别
图解23种设计模式(TypeScript版)——前端必修内功心法
观察者模式 vs 发布订阅模式
设计模式之发布订阅模式(1) 一文搞懂发布订阅模式
github - node/lib/events
github - wxpage/lib/message
Node中EventEmitter理解与简单实现
网友评论