1、什么是发布 — 订阅模式

在23种设计模式中没有发布-订阅模式的,其实它是发布订阅模式的一个别名,但两者又有所不同。这个别名非常形象地诠释了观察者模式里两个核心的角色要素——发布者订阅者

很多人在微博上关注了A,那么当A发布微博动态的时候微博就会为我们推送这个动态。在这个例子中,A就是发布者,我们是订阅者,微博就是调度中心,我们和A之间是没有直接信息来往的,都是通过微博平台来协调的,这就是发布-订阅模式。

虽然发布-订阅模式是观察者模式的一个别名,但是发布-订阅模式经过发展,已经独立于观察者模式,成为一种比较重要的设计模式。

较观察者模式的最大区别就是发布-订阅模式有一个调度中心,观察者模式是由具体目标调度的,而发布-订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布-订阅模式则不会,这就实现了解耦。

2、发布-订阅模式的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/*
* @Description:
* @Author: xiuji
* @Date: 2022-11-08 16:51:19
* @LastEditTime: 2022-11-09 15:03:39
* @LastEditors: Do not edit
*/



/**
* 发布订阅者
* 消息队列(缓存列表/调度中心)——用于存放订阅者和订阅者绑定的对应事件
* $on——向消息队列中添加内容
* $off——根据事件类型删除消息队列中存放的内容
* $emit——根据事件类型触发对应消息队列中的方法
*/
class observe {
constructor () {
this.message = {}
}
/**
*
* @param {string} type 被观察事件名
* @param {callback} fn 被观察事件发生后触发的事件
*/
$on(type,fn) {
// 向缓存列表/调度中心中添加事件和方法前先判断缓存列表中是否已存在对应事件
if (!this.message[type]) {
// 没有这个属性初始化一个空数组
this.message[type] = []
}
// 有这个属性就往它后面push一个新的fn
this.message[type].push(fn)
}
/**
*
* @param {string} type 要进行删除操作的消息队列
* @param {callback} fn 指定要删除的方法
*/
$off(type,fn) {
// 判断缓存列表/调度中心中是否存在对应事件
if (!this.message[type]) return
// 存在对应事件,判断删除的对象是整个消息队列还是具体的方法;删除具体方法先判断是否存在该方法
if (!fn) {
this.message[type] = undefined
return
}
this.message[type] = this.message[type].filter((ele) => ele !== fn);
}
/**
*
* @param {string} type 通过事件名触发对应动作
*/
$emit(type) {
// 首先判断是否存在该事件
if (!this.message[type]) return
this.message[type].forEach(ele => {
ele()
});
}
}

// 使用构造方法创建一个实例
let example = new observe()

// 向实例委托一些需要观察的内容——(事件名,观察到事件发生后需要触发的事件)。例如网上商品预售模式,消费者付定金,商家到货后自动帮消费者下单
example.$on('购买牙刷',purchaseToothbrush)
example.$on('购买牙刷', purchaseNoodles)

// 置空整个消息队列
// example.$off('购买牙刷')
// 删除当前队列中的purchaseToothbrush方法
// example.$off('购买牙刷', purchaseToothbrush)
// 触发事件
example.$emit('购买牙刷')
// 自定义事件发生时随之需要触发的事件
function purchaseToothbrush() {
console.log('牙刷已下单');
}
function purchaseNoodles() {
console.log('面条已下单');
}

3、发布-订阅模式的优缺点

发布-订阅模式的最大优点就是解耦:

  • 时间上的解耦:注册的订阅行为由消息的发布方来决定何时调用,订阅者不用持续关注,当消息发生时发布者会负责通知;
  • 对象上的解耦:发布者不用提前知道消息的接受者是谁,发布者只需要遍历处理所有订阅该消息类型的订阅者发送消息即可(迭代器模式),由此解耦了发布者和订阅者之间的联系,互不持有,都依赖于抽象,不再依赖于具体;

由于它的解耦特性,发布-订阅模式的使用场景一般是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变。

发布-订阅模式也有缺点:

  • 增加消耗:创建结构和缓存订阅者这两个过程需要消耗计算和内存资源,即使订阅后始终没有触发,订阅者也会始终存在于内存;
  • 增加复杂度:订阅者被缓存在一起,如果多个订阅者和发布者层层嵌套,那么程序将变得难以追踪和调试。

缺点主要在于理解成本、运行效率、资源消耗,特别是在多级发布-订阅时,情况会变得更复杂。

设计模式主要在于学习其中的设计思想,完成编程思维的进阶