结合代码白话理解javascript中的观察者模式与发布订阅

1 概念解释:

(1)观察者设计模式

标准解释:
1
观察者模式: 是一种设计模式,在软件设计中往往是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们。

来源:维基百科

图示:
1
2
3
4
5
╭─────────────╮  Fire Event  ╭──────────────╮
│ │─────────────>│ │
│ Subject │ │ Observer │
│ │<─────────────│ │
╰─────────────╯ Subscribe ╰──────────────╯
白话讲解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
假设你想去xx公司应聘前端工程师,但xx公司hr告知你目前没有岗位空缺,你向留下了联系方式,让其在有岗位空缺时,通知你。当然公司还会通知同样已留下联系方式的其他候选者。

Observers(观察者): 候选人

Subject(被观察者): xx公司

Fire Event (事件): xx公司有了新前端空缺

Subscribe(订阅): 候选人将联系方式留给了xx公司

Notify (通知) : xx公司向候选人致电

观察者模式运行流程:

Observers -> Subscribe -> Subject -> Fire Event -> Notify -> Observers

(2)发布订阅模式

标准解释:
1
2
3
4
5

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


发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式

来源:维基百科

图示:
1
2
3
4
5
╭─────────────╮                 ╭───────────────╮   Fire Event   ╭──────────────╮
│ │ Publish Event │ │───────────────>│ │
│ Publisher │────────────────>│ Event Channel │ │ Subscriber │
│ │ │ │<───────────────│ │
╰─────────────╯ ╰───────────────╯ Subscribe ╰──────────────╯
白话讲解:
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
- 你想成为一个前端工程师,但目前没有明确目标,你可以联系了猎头让其有前端相关岗位时通知你。

- 猎头可能与多家公司都有合作,这些公司发布职位会告知猎头

- 这个过程xx公司,只需要根据自身情况,只需要根据自己实际的岗位情况,告知猎头自己需要什么岗位的候选人,无需知道有哪些具体候选人,候选人在订阅过程中,只是向猎头公司订阅了有前端相关岗位时通知自己


Publisher(发布者): xx公司

Publish Event(发布事件): 发布职位

Event Channel (事件通道): 猎头公司

Fire Event(事件): xx公司发布职位后,猎头公司根据职位发送联系相应岗位候选人

Subscriber(订阅者): 候选人

Subscribe(订阅): 候选人将联系方式留给hr



运行流程:

Subscriber -> Subscribe -> Event Channel

Publisher -> Publish Event -> Event Channel -> Fire Event -> Subscriber

2 观察者模式和发布订阅模式有什么不同:

发布订阅模式属于广义上的观察者模式。发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式。

区别:

  • 发布订阅模式多了个事件通道

在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应

  • 在发布订阅模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接收事件,另一方面向订阅者发布事件;订阅者需要从事件通道订阅事件

以此避免发布者和订阅者之间产生依赖关系。

3. 观察者模式代码实现

使用观察者模式的好处:

  • 支持简单的广播通信,自动通知所有已经订阅过的对象。
  • 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
  • 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

最简单观察者模式在js中的运用

1
2
3
document.body.addEventListener('click', function () {
console.log('body 被观察')
});

观察者模式实现数据打包下载功能:

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
// 观察者模式 实现下载任务

/**
* 观察者
* @param id
* @constructor
*/

function DownloadTask(id) {
this.id = id;
this.loaded = false;
this.url = null;
}

DownloadTask.prototype.finish = function(url) {
this.loaded = true;
this.url = url;
console.log('Task ' + this.id + ' load data from ' + url);
};

/**
* 被观察者
* @constructor
*/

function DataHub() {
this.downloadTasks = []; // 观察者列表
}

DataHub.prototype.addDownloadTask = function(downloadTask) {
this.downloadTasks.push(downloadTask);
};

DataHub.prototype.removeDownloadTask = function(downloadTask) {
this.downloadTasks.filter(function (item) {
if (item === downloadTask) {

}
})
// this.downloadTasks.remove(downloadTask);
};

DataHub.prototype.notify = function(url) {
const downloadTaskCount = this.downloadTasks.length;
for (var i = 0; i < downloadTaskCount; i++) {
this.downloadTasks[i].finish(url);
}
};

// 使用

var d = new DataHub();

var task1 = new DownloadTask(1);
var task2 = new DownloadTask(2);

d.addDownloadTask(task1);
d.addDownloadTask(task2);

d.removeDownloadTask(task1);

d.notify('http://somedomain.someaddress');

4. 发布订阅代码实现

  • 发布订阅模式属于广义上的观察者模式
1
发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式
  • 发布订阅模式多了个事件通道
1
在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应
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
// 发布者
function Observer () {
this.fnList = []; // 订阅者列表
}

Observer.prototype = {
subscribe: function (fn) { // 增加订阅者
this.fnList.push(fn);
},

unsubscribe: function (fn) { // 移除订阅者
this.fnList = this.fnList.filter(function (el) {
if (el !== fn){
return el;
}
})
},

notify: function (p, thisObj) { //发布消息函数
var scope = thisObj || window;
this.fnList.forEach(
function (el) {
el.call(scope, o);
}
);
console.log(this.fnList);
}

};

var o = new Observer;

var f1 = function (data) {
console.log('Robbin: ' + data + ', 赶紧干活了!');
};

var f2 = function (data) {
console.log('Randall: ' + data + ', 找他加点工资去!');
};


o.subscribe(f1);
o.subscribe(f2);

o.notify("Tom回来了!");

//退订f1
o.unsubscribe(f1);
//再来验证
o.notify("Tom回来了!");

发布定远模式实现数据打包下载功能:

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
// 发布者

function DataHub() {}

DataHub.prototype.notify = function(url, callback) {
callback(url);
}


// 事件通道

function DownloadManager() {
this.events = {};
this.uId = -1;
}

DownloadManager.prototype.publish = function(eventType, url) {
if (!this.events[eventType]) {
return false;
}
var subscribers = this.events[eventType],
count = subscribers ? subscribers.length : 0;
while (count--) {
var subscriber = subscribers[count];
subscriber.handler(eventType, subscriber.taskId, url);
}
}

DownloadManager.prototype.subscribe = function(eventType, handler) {
if (!this.events[eventType]) {
this.events[eventType] = [];
}
var taskId = (++this.uId).toString();
this.events[eventType].push({
taskId: taskId,
handler: handler
});

return taskId;
}

// 创建一个数据中心
var dataHub = new DataHub();

// 创建一个下载事件管理器
var downloadManager = new DownloadManager();

// 创建一个下载器
var dataLoader = function(eventType, taskId, url) {
console.log('Task ' + taskId + ' load data from ' + url);
}

// 用户来请求数据了
var downloadTask1 = downloadManager.subscribe('dataReady', dataLoader);

// 数据打包完成了

dataHub.notify('http://somedomain.someaddress', function(url){
downloadManager.publish('dataReady', url);
});
Share