前言
在一次面试中,被问到了观察者模式和发布订阅模式有什么区别?
以我之前在网上冲浪了解过的经验,然后以灿烂的笑容回答:这两者是一样的!!
结果面试官笑着说:不,它们不一样
回到家后,通过各种渠道搜索找到答案。下面来做一个记录
观察者模式
- 网上的解释
有一个主对象,维护其依赖者的列表,称为观察者,并自动通知他们状态的变化,通常是调用他们的一个方法
是不是很抽象,让我们用人话解释一下
假设你正在找工作,公司名是(XX公司),联系了他们的招聘经理,他向你保证,如果职位有空缺,会立刻通知你,不过除了你之外还有其他候选人,我也会顺便通知他们的
在这里,XX公司是主题(Subject),他维护着观察者的列表(你和候选人),并针对事件(职位空缺)通知观察者(你们)。
下面,让我们通过代码来实现观察者模式!
页面上有一个按钮,点击的时候会在下面添加一个checkbox,这个checkbox是一个观察者,
点击全选的时候,会触发notify,实现全选的效果
<button id="add">addToCheckBox</button>
<input id="controlCheckbox" type="checkbox"/>全选
<p id="contain"></p>
复制代码
// 获取dom元素
const addCheck = document.querySelector('#add') // 增加一个checkbox的按钮
const controlCheckbox = document.querySelector("#controlCheckbox") // 全选的按钮
const contain = document.querySelector('#contain') // 插入checkbox的容器
// 内部维护了一个observerList
class Subject {
observers = new ObserverList()
/**
* 调用内部维护的ObserverList的add方法
* @params {observer} observer对象
* */
addObserver = observer => {
this.observers.add(observer)
}
/**
* 通知函数,用于通知观察者并且执行update函数,update是一个实现接口的方法,是一个通知的触发方法。
* */
notify = () => {
const len = this.observers.count() // 拿到observers的长度
for (let i = 0; i < len; i++) {
this.observers.get(i).update() // 调用observer的update方法
}
}
}
/*
* ObserverList
* 内部维护了一个数组,3个方法用于数组的操作,这里相关的内容还是属于subject,因为ObserverList的存在是为了将subject和内部维护的observers分离开来,清晰明了的作用。
*/
class ObserverList {
observerList = []
/**
* 添加一个订阅数组
*/
add = observer => {
this.observerList.push(observer)
}
/**
* 添加一个订阅数组
*/
count = () => {
return this.observerList.length
}
/**
* 获取指定下标的observer
*/
get = i => {
return this.observerList[i]
}
}
/*
* The Observer
* 提供更新接口,为想要得到通知消息的主体提供接口。
*/
class Observer {
constructor () {
this.update = function () {
console.log('我被通知了')
}
}
}
// 合并
function extend(obj, extension){
for (let key in extension ){
obj[key] = extension[key];
}
}
// 把Subject 方法合并到节点controlCheckbox中
extend(controlCheckbox, new Subject())
// 点击选中的时候更新数据
controlCheckbox.onclick = function () {
controlCheckbox.notify() // 通知其他observer
}
addCheck.onclick = () => {
// 创建一个Input
const ipt = document.createElement('input')
ipt.type = 'checkbox'
//用观测器类扩展复选框
extend(ipt, new Observer())
// 用自定义更新行为重写,在这里做勾选
ipt.update = function () {
console.log('我被更新了')
this.checked = !this.checked
}
// 将新观察者添加到我们的观察者列表中
controlCheckbox.addObserver(ipt)
// 添加到页面上
contain.appendChild(ipt)
}
复制代码
哼哼,代码有点多,这样就实现了观察者模式。Vue中数据响应也是用的该模式!
Pub-Sub (发布订阅者模式)
它在概念上与观察者非常相近,观察者模式的主题(Subject)就像发布者,观察者(Observers)就像订阅者。实际上,两种模式的主要区别是
在发布订阅者模式中,称为发布者的消息发送人不会将消息发送给直接的接收者
这意味着发布者和订阅者不知道彼此的存在,有第三方的存在(这里称为组件,也可以称为Broker),称为代理或消息总线或事件总线,发布者和订阅者都知道该组件,该组件会过滤所有传入的消息并相应的分发他们
按照惯例,还是举一个说人话的例子
假设你正在找工作,公司名是(XX公司),由于投递简历的人太多,招聘经理忙不过来了,招了几个HR来负责招聘,每个HR招聘的岗位不同,在让HR来给予你反馈。
在这里,发布者是招聘经理,Broker是HR,你是订阅者
下面,我们用代码来实现
const pubsub = {}
function initPubsub (pubsub) {
// 可以广播的主题的存储
const topics = {}
// 标识符,每一个订阅者唯一token
let subId = -1
/**
* 发布或广播感兴趣的事件
* @param {string} topic 具有特定的主题名称
* @param {string} args 参数
* */
pubsub.publish = function (topic, args) {
const subscribers = topics[topic] // 拿到这个订阅者
if (!subscribers || subscribers.length === 0) return // 如果没有这个订阅者,返回
let len = subscribers.length // 拿到订阅者的长度
// 依次调用订阅者函数
while (len--) {
subscribers[len].func(topic, args)
}
return this
}
/**
* 订阅感兴趣的事件
* @param {string} topic 具有特定的主题名称
* @param {function} func 回调函数
*/
pubsub.subscribe = function (topic, func) {
if (!topics[topic]) topics[topic] = [] // 如果没有这个订阅者,设置为空数组
const token = (++subId).toString() // 唯一token
topics[topic].push({ token, func }) // 存储订阅者
// 把token返回
return token
}
/**
* 取消订阅
* @param {string} token
*/
pubsub.unsubscribe = function (token) {
for (const topic in topics) { // 循环广播存储
const subscribers = topics[topic]
const index = subscribers.findIndex(({ token: subToken }) => subToken === token) // 找到这个token
if (index === -1) continue
subscribers.splice(index, 1) // 删除
}
}
}
// 初始化发布订阅 也可以用自执行函数
initPubsub(pubsub)
// 以下是测试代码
const sub1 = pubsub.subscribe('message', (topic, args) => {
console.log(`类型是${topic}, 参数是:`, args)
})
const sub2 = pubsub.subscribe('hhh', (topic, args) => {
console.log(`我是第二个啦类型是${topic}, 参数是:`, args)
})
pubsub.publish('message', 123)
pubsub.publish('message', [1,2,3])
pubsub.publish('message', { a: 1, b: 2 })
pubsub.unsubscribe(sub2)
pubsub.publish('message', 214141242)
pubsub.publish('hhh', 'hhh')
复制代码
发布订阅比观察者模式要容易理解很多,哈哈,下面让我们来做一个总结!
总结
因此,这两种的主要区别可以用下图来说明。
让我们快速总结出差异:
- 在观察者模式中,观察者知道主题,主题也维护观察者的记录。而在发布订阅模式中,发布者和订阅者不需要彼此了解。他们只是在消息队列或代理的帮助下进行通信。
- 观察者模式,是松散耦合的,发布订阅模式相反,完全不耦合。
- 观察者模式主要以同步方式实现,当某个事件发生时,主题调用所有观察者的方法,发布订阅模式大多是异步模式(使用消息队列)
- 观察者模式基本用于单个应用内部,发布订阅模式更多的是跨应用的模式。
结语
文章参考自(感谢这位大佬的文章):Observer vs Pub-Sub pattern
感谢你花了这么长时间看到这里,如果文章有任何错误的地方或者有什么建议,欢迎评论区交流!