惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
H
Hacker News: Front Page
Stack Overflow Blog
Stack Overflow Blog
B
Blog
I
InfoQ
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
F
Fortinet All Blogs
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
月光博客
月光博客
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
F
Full Disclosure
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recent Announcements
Recent Announcements
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Jina AI
Jina AI
T
Tailwind CSS Blog
S
Secure Thoughts
P
Privacy International News Feed
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LINUX DO - 最新话题
H
Hackread – Cybersecurity News, Data Breaches, AI and More
C
Cybersecurity and Infrastructure Security Agency CISA
Last Week in AI
Last Week in AI
W
WeLiveSecurity
Google Online Security Blog
Google Online Security Blog
P
Privacy & Cybersecurity Law Blog
D
DataBreaches.Net
Engineering at Meta
Engineering at Meta
Know Your Adversary
Know Your Adversary
P
Palo Alto Networks Blog
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Project Zero
Project Zero
V2EX - 技术
V2EX - 技术
H
Heimdal Security Blog
博客园 - Franky
阮一峰的网络日志
阮一峰的网络日志
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Troy Hunt's Blog
V
Vulnerabilities – Threatpost
H
Help Net Security
Martin Fowler
Martin Fowler
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
G
GRAHAM CLULEY
博客园 - 【当耐特】

MARKSZのBlog

一次由 pnpm 小版本差异引发的 PicGo 2.5.3 发布事故 极空间虚拟机安装 Ubuntu 24.04 踩坑记录 | MARKSZのBlog PicGo 的签名与公证 | MARKSZのBlog 写在 PicGo 即将 8 周年之际 Vite 原理浅析 | MARKSZのBlog Typora 支持 PicGo 来上传图片了 | MARKSZのBlog 用setTimeout和clearTimeout简单实现setInterval与clearInterval | MARKSZのBlog 我的2019春招(暑期实习)记录 | MARKSZのBlog Electron-vue开发实战7——命令行调用与系统级别右键菜单的实现 | MARKSZのBlog Electron-vue开发实战6——开发插件系统之GUI部分 | MARKSZのBlog Electron-vue开发实战5——开发插件系统之CLI部分 | MARKSZのBlog 2018小结 | MARKSZのBlog 图床「神器」PicGo v2.0更新,插件系统终于来了 | MARKSZのBlog 一周一部好电影V【WEEK210 网络迷踪】 | MARKSZのBlog PicGo的star数破1000的心路历程 | MARKSZのBlog 小记VSCode插件amVim的改进以及插件开发 | MARKSZのBlog 基于Koa2开发微信二维码扫码支付相关流程 | MARKSZのBlog 【NOTE】进程-线程-协程 关系与区别 | MARKSZのBlog 基于Electron-vue的图床上传工具PicGo v1.5更新说明 | MARKSZのBlog
【NOTE】观察者模式VS订阅发布模式 | MARKSZのBlog
Molunerfinn · 2018-05-13 · via MARKSZのBlog

最近在看了一篇《不好意思,观察者模式跟发布订阅模式就是不一样》的文章之后对于这两个模式产生了比较浓厚的兴趣。不过奈何我的水平有限,看完那篇文章还是不能理解。不过在和朋友讨论之后,我想我应该是弄懂了。所以特地记下一篇笔记,以便回头翻阅的时候能够想起来。如果理解有误,欢迎在下方评论指出,一起讨论!

概述

有人说这两个模式其实是一个模式。我想这句话的对错对半分吧。它们有类似的地方,不过也不能说完全一致。先来一张图,这张图解释了观察者模式发布订阅模式在流程上的一些区别:

左边是观察者模式,右边是订阅发布模式。

简单阐述二者的模型:

观察者模式里,观察者(Observer)直接订阅(subscribe)主题(Subject),而当主题被激活的时候,会触发(fire)观察者里的事件。

订阅发布模式里,订阅者(Subscriber)通过监听(on)事件总线(Event Bus)里的事件,当事件总线里的事件被触发(emit)的时候,订阅者将会执行相应的操作。而这里需要注意的是,事件总线里的事件是通过发布者(Publisher)进行发布(publish)和 通知事件总线 触发 的。

注:事件总线也有说法叫为调度中心。本质上是一样的。不过因为写Vue时候习惯用Event Bus来说了,所以本文的调度中心皆以事件总线称呼。

所以事件总线本身不独自发布和触发事件,它会借由发布者来操作。这是跟观察者模式有着比较大的区别的地方。

当然只看这两张图和上面的解释,应该还是无法很好的理解。下面这张图能把流程讲得更清楚点。

这个例子可以理解为这样:左边是微信里的微商-顾客之间的关系。右边是商家-淘宝-顾客之间的关系。

观察者模式:顾客关注了微商的商品,微商会记住顾客关注的商品,一旦上新就直接 私聊 通知所有关注这个商品的顾客。这里的顾客就相当于观察者,这里的微商就相当于主题。
订阅发布模式:顾客通过淘宝(APP或者网站)关注了商家的商品,商家一旦上新就通过淘宝(APP或者网站)向关注了它的顾客 群发 消息。这里的顾客就是订阅者,这里的淘宝就是事件总线,这里的商家就是发布者。

所以可以看出,观察者模式的模型跟发布订阅模型里,差距就差在有没有一个中央的事件总线。如果有这个事件总线,我们就可以认为是个发布订阅模型。如果没有,那么就可以认为是个观察者模型。因为其实它们都实现了一个关键的功能:发布事件-订阅事件并触发事件。

下面用代码简单解释一下。

观察者模式

由于最近在学习TypeScript,所以下面的代码也会用TypeScript来书写。

我们先写一个定义观察者和主题的文件。

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


interface Subjects {
[key: string]: any
}

class Observer {
subject: string
constructor (subject: string) {
this.subject = subject
}
notify () {
console.log(`This ${this.subject} was fired!`)
this.subject = `Done`
}
}


class Subject {

subjects: Subjects = {}

add (subject, observer: Observer): void {
if (!this.subjects[subject]) {
this.subjects[subject] = []
}
this.subjects[subject].push(observer)
}

remove (subject, observer: Observer): void {
this.subjects[subject].forEach((item, index) => {
if (item === observer) {
this.subjects[subject].splice(index, 1)
}
})
}

fire (subject): void {
this.subjects[subject].forEach(item => item.notify())
}
}

export {
Observer,
Subject
}

于是在调用的时候,是这样调用的:

1
2
3
4
5
import * as op from './observer-pattern'
let observer = new op.Observer('click')
let subjects = new op.Subject()
subjects.add('click', observer)
subjects.fire('click')

经过上述调用,subjects触发观察者订阅的click事件,observer.subject的值将会变为Done(原先为click)。

订阅发布模式

接下来我们来实现一些订阅发布模式。订阅发布模式最关键的地方就在于中间的Event Bus部分。它接管着事件总线的订阅和发布。

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


interface Subjects {
[key: string]: any
}


class EventBus {
subjects: Subjects = {}
on (subject, callback): void {

if (!this.subjects[subject]) {
this.subjects[subject] = []
}
this.subjects[subject].push(callback)
}
off (subject, callback = null): void {
if (callback === null) {
this.subjects[subject] = []
} else {
this.subjects[subject].forEach((item, index) => {

if (item === callback) {
this.subjects[subject].splice(index, 1)
}
})
}
}
emit (subject, data = null): void {
this.subjects[subject].forEach(item => item(data))
}
}

export default new EventBus()

可以看出在这里的EventBus和观察者模式里的Subject几乎一致对吧。但是需要注意的是,最后一行里,我们export default new EventBus(),所以我们在项目里不同的地方import它,都会指向同一个Event Bus实例,这样的话就可以起到一个事件总线的作用了。它不在乎谁来监听,谁来发布。只要有人监听了,就把它放进监听队列中。只要有人发布了事件,就从相应的监听队列中触发回调。不过所有相关的事件都必须经过Event Bus这个实例,而不能越过它直接由发布者通知监听者。

再次祭出这张图

所以在订阅发布模型里,发布者或者订阅者的身份已经被弱化。发布者可以在任何时候发布事件,而订阅者可能只是一个回调函数。而最关键的事件总线部分,则是发布订阅模型的核心。

如果你用过Vue的Event Bus,相信不会陌生。接下来我们来用用我们刚才写的简单的Event Bus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import bus from './pubsub.ts'
const people = function (val) {
console.log('我收到了新的商品通知:', val)
}

bus.on('newItem', people)

const merchant = function (val) {
const item = {
item: val
}
bus.emit('newItem', item)
}

merchant('Book')

所以你可以看到,这个事件总线是可以单独抽离出来的。如果要把我们这个文件丢到一个现有的项目里也是完全没问题的。

其实在写Vue组件通信的时候,你如果用到了Event Bus的话,也是一样的。在全局声明一个new Vue()Event Bus总线,然后在不同的组件里只要引入了这个事件总线,就能订阅或者发布不同的消息。这个就是一个非常典型的订阅发布模型。

而如果只是Vue的父子组件通信,子组件用的是this.$emit来触发事件,父组件用的是this.$on这样的方式去订阅事件,那么你可以认为这个就是一个简单的观察者模型。因为它们之间的联系是紧密耦合的。

总结

不管是观察者模式也好,订阅发布模式也好,关键在于实现了在某个特定时间触发某个特定事件,从而触发监听这个特定事件的组件进行相应操作的功能。这个设计模式在很多时候非常有用。平时只是用到了它,但是没有深入去看看如何实现,这次借由这个机会把二者的关系和区别记录下来,也算是给自己加深了印象。

本文的代码你可以在我的学习仓库FE-Learning找到。如有错误欢迎指出!

参考资料

https://www.zcfy.cc/article/observer-vs-pub-sub-pattern-hacker-noon

http://blog.zxbing0066.com/design-patterns/2016/09/12/observer-pattern.html

https://juejin.im/post/5af05d406fb9a07a9e4d2799

https://www.cnblogs.com/weebly/p/5279952.html

https://www.jianshu.com/p/3098b1176357

https://www.zhihu.com/question/23486749/answer/314072549

Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.