curTain

我看了很多关于发布订阅模式与观察者模式的文章,有点朋友认为观察者模式就是发布订阅模式,根据我看了这么多的文章的总结,他们确实是不一样的。

在vue中,emit和on方法。他们都似乎不约而同的自带了发布订阅属性一般,让开发变得更加高效好用起来。

今天就写一个发布订阅。

1. 情景模拟:

当我们在注册虎扑的时候,软件会让我们选择我们感兴趣的板块,例如:唱、跳、rap、篮球…..

当我们选择自己喜欢的版块时,我们就订阅了相关板块的消息。

当此板块内有热门消息时,虎扑就会给我们推送此消息,他不会把其他板块的消息推送给我们。

2. 分析:

我们可以抽象出三个对象,一个发布者,一个调度中心,一个订阅者

发布者:我就是在不同板块发送热门消息的

调度中心:存下所有板块,与板块对应的订阅者

             一个函数:接收消息并给相关板块所有的订阅者发送消息

             一个函数:移除订阅者

             一个函数:添加订阅者

订阅者:自己的属性

             一个函数:订阅消息

             一个函数:取消订阅消息

画出 UML 类图就是文章前的图片。

3. 代码实现

真实代码放在文章末尾。

结果:

4. 总结

什么是发布订阅模式:

发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。

说说我的理解:

发布订阅模式可以理解成三个对象,一个是发布者,一个是订阅者,一个是调度中心。

为什么需要一个调度中心?因为订阅者的订阅是有一定要求的,而不是接收所有发布的消息,

所以调度中心就是将消息分别发送给订阅者。订阅者不会接收到没有订阅的消息。

观察者模式:

观察者只有两个对象,一个观察者、一个被观察者,被观察者只要作出变化(不论什么变化),所有的观察者都会接受到消息。

5. 参考材料

发布订阅模式,在工作中它的能量超乎你的想象

谈谈观察者模式和发布订阅模式

EventBus—思考观察者模式与发布订阅者模式

JavaScript中发布/订阅模式的理解

6. 代码:

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
// 发布者   传入调度中心实例
class Publisher {
constructor( dispatch ){
this.dispatch = dispatch
}
publish(){
let arg = Array.from( arguments )
let event = arg.shift()
this.dispatch.emit( event, arg )
}
}
// 调度中心
class Dispatcher {
constructor(){
this.eventObj = {}
}
on( event, fn ){
if( !this.eventObj[event] ){
this.eventObj[event] = []
}
this.eventObj[event].push( fn )
}
emit( type, arg ){
let funs = this.eventObj[type]
if( !funs || funs.length === 0 ){
return
}
funs.forEach( item => {
item.apply( this, arg )
});
}
remove( type, fn ){
let fns = this.eventObj[ type ]
if( !fns ) return
if( !fn ){
delete this.eventObj[type]
} else {
this.eventObj[ type ] = fns.filter( item => item !== fn )
}
}
}
// 订阅者类 传入 dispatch 和 自己的属性
class Subscriber {
constructor( dispatch, name ){
this.dispatch = dispatch
this.name = name
}
fn = ( msg ) => {
console.log( `我是${ this.name }:` ,"收到订阅的消息:", msg )
}
subscrib( type, fn ){
this.dispatch.on( type, fn, this )
}
removeSubscrib( type, fn ){
this.dispatch.remove( type, fn )
}
}
// 测试
let dispatch = new Dispatcher()
let pub = new Publisher( dispatch )
let sub1 = new Subscriber( dispatch, "sub1" )
let sub2 = new Subscriber( dispatch, "sub2" )

// 订阅消息
sub1.subscrib( "篮球", sub1.fn )
sub2.subscrib( "篮球", sub2.fn )
sub2.subscrib( "足球", sub2.fn )

// 发布消息
pub.publish( "篮球", "乔丹去世了---" )
pub.publish( "足球", "国足能进世界杯吗??" )
// 取消订阅
sub1.removeSubscrib( "篮球", sub1.fn )
pub.publish( "篮球", "这里有一些NBA相关的信息" )

通用代码:

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
let event = {
list: {},
on(key, fn) {
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
},
emit() {
let key = [].shift.call(arguments),
fns = this.list[key];

if (!fns || fns.length === 0) {
return false;
}
fns.forEach(fn => {
fn.apply(this, arguments);
});
},
remove(key, fn) {
// 这回我们加入了取消订阅的方法
let fns = this.list[key];
// 如果缓存列表中没有函数,返回false
if (!fns) return false;
// 如果没有传对应函数的话
// 就会将key值对应缓存列表中的函数都清空掉
if (!fn) {
fns && (fns.length = 0);
} else {
// 遍历缓存列表,看看传入的fn与哪个函数相同
// 如果相同就直接从缓存列表中删掉即可
fns.forEach((cb, i) => {
if (cb === fn) {
fns.splice(i, 1);
}
});
}
}
};

function cat() {
console.log('一起喵喵喵');
}
function dog() {
console.log('一起旺旺旺');
}

event.on('pet', data => {
console.log('接收数据');
console.log(data);
});
event.on('pet', cat);
event.on('pet', dog);
// 取消dog方法的订阅
event.remove('pet', dog);
// 发布
event.emit('pet', ['二哈', '波斯猫']);
/*
接收数据
[ '二哈', '波斯猫' ]
一起喵喵喵
*/

摘自:

发布订阅模式,在工作中它的能量超乎你的想象


 评论