TS 设计模式08 - 发布订阅模式
1. 简介
前面介绍了观察者模式,就好比我们去点餐,通知服务员说,餐好了跟我说一下。那么服务员和顾客之间就形成了耦合,首先服务员得知道餐品好了以后通知那些顾客,其次,如果是多位服务员协作,每个服务员都需要知道这些顾客。
但事实上你发现去 kfc 点餐的时候,服务员并没有直接通知我们。而是采用叫号的方式。细想一下,你去 kfc,是不是可以在点餐系统进行排号(网上或者排队,这里抽象一下),餐品好了以后,服务员输入点餐号,点一下完成即可,点餐系统会通知对应的顾客取餐。
这里你和服务员之间的消息通过点餐系统来传递,你并不需要知道是谁点的完成,服务员也不需要知道这份餐品给谁。完美解耦了消息的发送者和接收者。更好地是,我们在点餐或者叫号的时候其实还可以指定行为,比如说 66 号产品好了以后帮我送到 A1 桌。
再比如说炒股的时候,我们可以委托挂单,就是当股票到了某一个价格就帮你买入或者卖出,等等,例子很多。
2. 用例图
3. 实现
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('晴');
4. 小结
发布订阅模式可以说是对观察者模式的进一步抽象。
我们通过消息中心对消息进行统一处理,那么这里通知者和消费者的关系其实被弱化了,它们可以是任意对象,通知者和消费者也可以是同一个对象,这种模式甚至在非对象也可以使用,即我们只关注发布和订阅行为本身,而不关心发布订阅者是谁。
参考
从发布订阅模式入手读懂Node.js的EventEmitter源码 使用typescript 写一个简单的事件监听/发布订阅模式的类 TypeScript 设计模式之发布-订阅模式 观察者模式和发布订阅模式的区别 图解23种设计模式(TypeScript版)——前端必修内功心法 观察者模式 vs 发布订阅模式 设计模式之发布订阅模式(1) 一文搞懂发布订阅模式 github - node/lib/events github - wxpage/lib/message Node中EventEmitter理解与简单实现
- 算法基础6:二叉树查找
- 通过UDP广播实现Android局域网Peer Discovering
- tensorflow读取数据-tfrecord格式
- 用Python使用C语言程序(Windows平台)
- 译文 | 在使用过采样或欠采样处理类别不均衡数据后,如何正确做交叉验证?
- 花式解释AutoEncoder与VAE
- 用CNN做句子分类:CNN Sentence Classification (with Theano code)
- MySQL与Python的交互
- 实时Android语音对讲系统架构
- ElasticSearch优化系列二:机器设置(内存)
- Tensorflow之 CNN卷积神经网络的MNIST手写数字识别
- 你听过算法也是可以贪心的吗?
- 前后端分离ueditor富文本编辑器的使用-Java版本
- Golang语言社区--Go语言基础第六节函数
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 打卡群刷题总结0928——整数拆分
- 面试官最爱问的 11道 Redis 面试题,我替你整理好了
- 打卡群刷题总结0929——计算各个位数不同的数字个数
- codeforces 1423K(数学+差分数组预处理)
- 电影大片里的代码究竟有多高级?
- 打卡群刷题总结0930——最大整除子集
- 机器学习中的常用编码方式(一)
- leetcode题目之1、2---两数相加
- pyplot做PR-curve
- Go - flag:命令行flags解析
- 走进Network Namespace学会容器网络调试
- 打卡群刷题总结1001——组合总和 Ⅳ
- Oracle数据字典
- 打卡群2刷题总结1002——搜索插入位置
- React太劝退,通过anu学合成事件