共计 9755 个字符,预计需要花费 25 分钟才能阅读完成。
前言
在之前两篇自测清单中,和大家分享了很多 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.js
import Vue from 'vue'
export const EventBus = new Vue();
- 间接在我的项目中的
main.js
全局挂载 Vue 实例化的后果。
// main.js
Vue.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 订阅公布模式》