前言
在之前两篇自测清单中,和大家分享了很多 JavaScript 基础知识,大家能够一起再回顾下~
本文是我在咱们团队外部“古代 JavaScript 突击队”分享的一篇内容,第二期学习内容为“设计模式”系列,我会将我负责分享的常识整顿成文章输入,心愿可能和大家一起温故知新!
“古代 JavaScript 突击队”学习总结:
- 《初中级前端 JavaScript 自测清单 - 1》
- 《初中级前端 JavaScript 自测清单 - 2》
- 《TypeScript 设计模式之观察者模式》
- 《TypeScript语法总结+我的项目(Vue.js+TS)实战》
2.公布-订阅模式 2020.08.14
一、模式介绍
1. 生存场景
最近刚毕业的学生 Leo 筹备开始租房了,他来到房产中介,跟中介形容了本人的租房需要,开开心心回家了。第二天,中介的小哥哥小姐姐为 Leo 列出符他需要的房间,并打电话约他一起看房了,最初 Leo 选中一套称心的房间,高高兴兴过来签合同,筹备开始新生存~
还有个大佬 Paul,筹备将手中 10 套房出租进来,于是他来到房产中介,在中介那边提供了本人要出租的房间信息,沟通好手续费,开开心心回家了。第二天,Paul 接到中介的好消息,房子租出去了,于是他高高兴兴过来签合同,开始收房租了~
下面场景有个须要特地留神的中央:
- 租户在租房过程中,不晓得房间具体房东是谁,到前面签合同才晓得;
- 房东在出租过程中,不晓得房间具体租户是谁,到前面签合同才晓得;
这两点其实就是前面要介绍的 公布-订阅模式 的一个外围特点。
2. 概念介绍
在软件架构中,公布-订阅模式是一种音讯范式,音讯的发送者(称为发布者)不会将音讯间接发送给特定的接收者(称为订阅者)。而是将公布的音讯分为不同的类别,无需理解哪些订阅者(如果有的话)可能存在。同样的,订阅者能够表白对一个或多个类别的趣味,只接管感兴趣的音讯,无需理解哪些发布者(如果有的话)存在。
公布-订阅是音讯队列范式的兄弟,通常是更大的面向消息中间件零碎的一部分。大多数音讯零碎在API中同时反对音讯队列模型和公布/订阅模型,例如Java音讯服务(JMS)。
这种模式提供了更大的网络可扩展性和更动静的网络拓扑,同时也升高了对发布者和公布数据的构造批改的灵活性。
二、 观察者模式 vs 公布-订阅模式
看完下面概念,有没有感觉与观察者模式很像?
但其实两者还是有差别的,接下来一起看看。
1. 概念比照
咱们别离为通过两种理论生存场景来介绍这两种模式:
- 观察者模式:如微信中 顾客-微商 关系;
- 公布-订阅模式:如淘宝购物中 顾客-淘宝-商家 关系。
这两种场景的过程别离是这样:
1.1 观察者模式
观察者模式中,生产顾客关注(如加微信好友)本人有趣味的微商,微商就会私聊发本人在卖的产品给生产顾客。
这个过程中,生产顾客相当于观察者(Observer),微商相当于察看指标(Subject)。
1.2 公布-订阅模式
接下来看看 公布-订阅模式 :
在 公布-订阅模式 中,生产顾客通过淘宝搜寻本人关注的产品,商家通过淘宝公布商品,当生产顾客在淘宝搜寻的产品,曾经有商家公布,则淘宝会将对应商品举荐给生产顾客。
这个过程中,生产顾客相当于订阅者,淘宝相当于事件总线,商家相当于发布者。
2. 流程比照
3. 小结
所以能够看出,观察者模式和公布-订阅模式差异在于有没有一个地方的事件总线。如果有,咱们就能够认为这是个公布-订阅模式。如果没有,那么就能够认为是观察者模式。因为其实它们都实现了一个要害的性能:公布事件-订阅事件并触发事件。
三、模式特点
比照完观察者模式和公布-订阅模式后,咱们大抵了解公布-订阅模式是什么了。接着总结下该模式的特点:
1. 模式组成
在公布-订阅模式中,通常蕴含以下角色:
- 发布者:Publisher
- 事件总线:Event Channel
- 订阅者:Subscriber
2. UML 类图
3. 长处
- 松耦合(Independence)
公布-订阅模式能够将泛滥须要通信的子系统(Subsystem)解耦,每个子系统独立治理。而且即便局部子系统勾销订阅,也不会影响事件总线的整体治理。
公布-订阅模式中每个应用程序都能够专一于其外围性能,而事件总线负责将音讯路由到每个订阅者手里。
- 高伸缩性(Scalability)
公布-订阅模式减少了零碎的可伸缩性,进步了发布者的响应能力。起因是发布者(Publisher)能够疾速地向输出通道发送一条音讯,而后返回到其外围解决职责,而不用期待子系统解决实现。而后事件总线负责确保把消息传递到每个订阅者(Subscriber)手里。
- 高可靠性(Reliability)
公布-订阅模式进步了可靠性。异步的消息传递有助于应用程序在减少的负载下持续安稳运行,并且能够更无效地解决间歇性故障。
- 灵活性(Flexibility)
你不须要关怀不同的组件是如何组合在一起的,只有他们独特恪守一份协定即可。
公布-订阅模式容许提早解决或者按计划的解决。例如当零碎负载大的时候,订阅者能够等到非顶峰工夫才接管音讯,或者依据特定的打算解决音讯。
4. 毛病
- 在创立订阅者自身会耗费内存,但当订阅音讯后,没有进行公布,而订阅者会始终保留在内存中,占用内存;
- 创立订阅者须要耗费肯定的工夫和内存。如果适度应用的话,反而使代码不好了解及代码不好保护。
四、应用场景
如果咱们我的项目中很少应用到订阅者,或者与子系统实时交互较少,则不适宜 公布-订阅模式 。
在以下状况下能够思考应用此模式:
- 应用程序须要向大量消费者播送信息。例如微信订阅号就是一个消费者量宏大的播送平台。
- 应用程序须要与一个或多个独立开发的应用程序或服务通信,这些应用程序或服务可能应用不同的平台、编程语言和通信协议。
- 应用程序能够向消费者发送信息,而不须要消费者的实时响应。
五、实战示例
1. 简略示例
- 定义发布者接口(Publisher)、事件总线接口(EventChannel)和订阅者接口(Subscriber):
interface Publisher<T> { subscriber: string; data: T;}interface EventChannel<T> { on : (subscriber: string, callback: () => void) => void; off : (subscriber: string, callback: () => void) => void; emit: (subscriber: string, data: T) => void;}interface Subscriber { subscriber: string; callback: () => void;}// 不便前面应用interface PublishData { [key: string]: string;}
- 实现具体发布者类(ConcretePublisher):
class ConcretePublisher<T> implements Publisher<T> { public subscriber: string = ""; public data: T; constructor(subscriber: string, data: T) { this.subscriber = subscriber; this.data = data; }}
- 实现具体事件总线类(ConcreteEventChannel):
class ConcreteEventChannel<T> implements EventChannel<T> { // 初始化订阅者对象 private subjects: { [key: string]: Function[] } = {}; // 实现增加订阅事件 public on(subscriber: string, callback: () => void): void { console.log(`收到订阅信息,订阅事件:${subscriber}`); if (!this.subjects[subscriber]) { this.subjects[subscriber] = []; } this.subjects[subscriber].push(callback); }; // 实现勾销订阅事件 public off(subscriber: string, callback: () => void): void { console.log(`收到勾销订阅申请,须要勾销的订阅事件:${subscriber}`); if (callback === null) { this.subjects[subscriber] = []; } else { const index: number = this.subjects[subscriber].indexOf(callback); ~index && this.subjects[subscriber].splice(index, 1); } }; // 实现公布订阅事件 public emit (subscriber: string, data: T): void { console.log(`收到发布者信息,执行订阅事件:${subscriber}`); this.subjects[subscriber].forEach(item => item(data)); };}
- 实现具体订阅者类(ConcreteSubscriber):
class ConcreteSubscriber implements Subscriber { public subscriber: string = ""; constructor(subscriber: string, callback: () => void) { this.subscriber = subscriber; this.callback = callback; } public callback(): void { };}
- 运行示例代码:
interface Publisher<T> { subscriber: string; data: T;}interface EventChannel<T> { on : (subscriber: string, callback: () => void) => void; off : (subscriber: string, callback: () => void) => void; emit: (subscriber: string, data: T) => void;}interface Subscriber { subscriber: string; callback: () => void;}interface PublishData { [key: string]: string;}class ConcreteEventChannel<T> implements EventChannel<T> { // 初始化订阅者对象 private subjects: { [key: string]: Function[] } = {}; // 实现增加订阅事件 public on(subscriber: string, callback: () => void): void { console.log(`收到订阅信息,订阅事件:${subscriber}`); if (!this.subjects[subscriber]) { this.subjects[subscriber] = []; } this.subjects[subscriber].push(callback); }; // 实现勾销订阅事件 public off(subscriber: string, callback: () => void): void { console.log(`收到勾销订阅申请,须要勾销的订阅事件:${subscriber}`); if (callback === null) { this.subjects[subscriber] = []; } else { const index: number = this.subjects[subscriber].indexOf(callback); ~index && this.subjects[subscriber].splice(index, 1); } }; // 实现公布订阅事件 public emit (subscriber: string, data: T): void { console.log(`收到发布者信息,执行订阅事件:${subscriber}`); this.subjects[subscriber].forEach(item => item(data)); };}class ConcretePublisher<T> implements Publisher<T> { public subscriber: string = ""; public data: T; constructor(subscriber: string, data: T) { this.subscriber = subscriber; this.data = data; }}class ConcreteSubscriber implements Subscriber { public subscriber: string = ""; constructor(subscriber: string, callback: () => void) { this.subscriber = subscriber; this.callback = callback; } public callback(): void { };}/* 运行示例 */const pingan8787 = new ConcreteSubscriber( "running", () => { console.log("订阅者 pingan8787 订阅事件胜利!执行回调~"); });const leo = new ConcreteSubscriber( "swimming", () => { console.log("订阅者 leo 订阅事件胜利!执行回调~"); });const lisa = new ConcreteSubscriber( "swimming", () => { console.log("订阅者 lisa 订阅事件胜利!执行回调~"); });const pual = new ConcretePublisher<PublishData>( "swimming", {message: "pual 公布音讯~"});const eventBus = new ConcreteEventChannel<PublishData>();eventBus.on(pingan8787.subscriber, pingan8787.callback);eventBus.on(leo.subscriber, leo.callback);eventBus.on(lisa.subscriber, lisa.callback);// 发布者 pual 公布 "swimming"相干的事件eventBus.emit(pual.subscriber, pual.data); eventBus.off (lisa.subscriber, lisa.callback);eventBus.emit(pual.subscriber, pual.data);/*输入后果:[LOG]: 收到订阅信息,订阅事件:running[LOG]: 收到订阅信息,订阅事件:swimming[LOG]: 收到订阅信息,订阅事件:swimming[LOG]: 收到发布者信息,执行订阅事件:swimming [LOG]: 订阅者 leo 订阅事件胜利!执行回调~ [LOG]: 订阅者 lisa 订阅事件胜利!执行回调~ [LOG]: 收到勾销订阅申请,须要勾销的订阅事件:swimming [LOG]: 收到发布者信息,执行订阅事件:swimming [LOG]: 订阅者 leo 订阅事件胜利!执行回调~ */
残缺代码如下:
interface Publisher { subscriber: string; data: any;}interface EventChannel { on : (subscriber: string, callback: () => void) => void; off : (subscriber: string, callback: () => void) => void; emit: (subscriber: string, data: any) => void;}interface Subscriber { subscriber: string; callback: () => void;}class ConcreteEventChannel implements EventChannel { // 初始化订阅者对象 private subjects: { [key: string]: Function[] } = {}; // 实现增加订阅事件 public on(subscriber: string, callback: () => void): void { console.log(`收到订阅信息,订阅事件:${subscriber}`); if (!this.subjects[subscriber]) { this.subjects[subscriber] = []; } this.subjects[subscriber].push(callback); }; // 实现勾销订阅事件 public off(subscriber: string, callback: () => void): void { console.log(`收到勾销订阅申请,须要勾销的订阅事件:${subscriber}`); if (callback === null) { this.subjects[subscriber] = []; } else { const index: number = this.subjects[subscriber].indexOf(callback); ~index && this.subjects[subscriber].splice(index, 1); } }; // 实现公布订阅事件 public emit (subscriber: string, data = null): void { console.log(`收到发布者信息,执行订阅事件:${subscriber}`); this.subjects[subscriber].forEach(item => item(data)); };}class ConcretePublisher implements Publisher { public subscriber: string = ""; public data: any; constructor(subscriber: string, data: any) { this.subscriber = subscriber; this.data = data; }}class ConcreteSubscriber implements Subscriber { public subscriber: string = ""; constructor(subscriber: string, callback: () => void) { this.subscriber = subscriber; this.callback = callback; } public callback(): void { };}/* 运行示例 */const pingan8787 = new ConcreteSubscriber( "running", () => { console.log("订阅者 pingan8787 订阅事件胜利!执行回调~"); });const leo = new ConcreteSubscriber( "swimming", () => { console.log("订阅者 leo 订阅事件胜利!执行回调~"); });const lisa = new ConcreteSubscriber( "swimming", () => { console.log("订阅者 lisa 订阅事件胜利!执行回调~"); });const pual = new ConcretePublisher( "swimming", {message: "pual 公布音讯~"});const eventBus = new ConcreteEventChannel();eventBus.on(pingan8787.subscriber, pingan8787.callback);eventBus.on(leo.subscriber, leo.callback);eventBus.on(lisa.subscriber, lisa.callback);// 发布者 pual 公布 "swimming"相干的事件eventBus.emit(pual.subscriber, pual.data); eventBus.off (lisa.subscriber, lisa.callback);eventBus.emit(pual.subscriber, pual.data);/*输入后果:[LOG]: 收到订阅信息,订阅事件:running[LOG]: 收到订阅信息,订阅事件:swimming[LOG]: 收到订阅信息,订阅事件:swimming[LOG]: 收到发布者信息,执行订阅事件:swimming [LOG]: 订阅者 leo 订阅事件胜利!执行回调~ [LOG]: 订阅者 lisa 订阅事件胜利!执行回调~ [LOG]: 收到勾销订阅申请,须要勾销的订阅事件:swimming [LOG]: 收到发布者信息,执行订阅事件:swimming [LOG]: 订阅者 leo 订阅事件胜利!执行回调~ */
2. Vue.js 应用示例
参考文章:《Vue事件总线(EventBus)应用具体介绍》 。
2.1 创立 event bus
在 Vue.js 中创立 EventBus 有两种形式:
- 手动实现,导出 Vue 实例化的后果。
// event-bus.jsimport Vue from 'vue'export const EventBus = new Vue();
- 间接在我的项目中的
main.js
全局挂载 Vue 实例化的后果。
// main.jsVue.prototype.$EventBus = new Vue()
2.2 发送事件
假如你有两个Vue页面须要通信: A 和 B ,A页面按钮上绑定了点击事件,发送一则音讯,告诉 B 页面。
<!-- A.vue --><template> <button @click="sendMsg()">-</button></template><script> import { EventBus } from "../event-bus.js";export default { methods: { sendMsg() { EventBus.$emit("aMsg", '来自A页面的音讯'); } }}; </script>
2.3 接管事件
B 页面中接管音讯,并展现内容到页面上。
<!-- IncrementCount.vue --><template> <p>{{msg}}</p></template><script> import { EventBus } from "../event-bus.js";export default { data(){ return { msg: '' } }, mounted() { EventBus.$on("aMsg", (msg) => { // A发送来的音讯 this.msg = msg; }); }};</script>
同理能够从 B 页面往 A 页面发送音讯,应用上面办法:
// 发送音讯EventBus.$emit(channel: string, callback(payload1,…))// 监听接管音讯EventBus.$on(channel: string, callback(payload1,…))
2.4 移除事件监听者
应用 EventBus.$off('aMsg')
来移除利用内所有对此某个事件的监听。或者间接用 EventBus.$off()
来移除所有事件频道,不须要增加任何参数 。
import { eventBus } from './event-bus.js'EventBus.$off('aMsg', {})
六、总结
观察者模式和公布-订阅模式的差异在于事件总线,如果有则是公布-订阅模式,反之为观察者模式。所以在实现公布-订阅模式,关键在于实现这个事件总线,在某个特定工夫触发某个特定事件,从而触发监听这个特定事件的组件进行相应操作的性能。公布-订阅模式在很多时候十分有用。
参考文章
1.《公布/订阅》
2.《观察者模式VS订阅公布模式》