共计 3054 个字符,预计需要花费 8 分钟才能阅读完成。
首选我们需要先了解两者的定义和实现的方式,才能更好的区分两者的不同点。
或许以前认为订阅发布模式是观察者模式的一种别称,但是发展至今,概念已经有了不少区别。
订阅发布模式
在软件架构中,发布 - 订阅 是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
或许你用过 eventemitter
、node 的 events
、Backbone 的 events
等等,这些都是前端早期,比较流行的数据流通信方式,即 订阅发布模式。
从字面意思来看,我们需要首先订阅,发布者发布消息后才会收到发布的消息。不过我们还需要一个中间者来协调,从事件角度来说,这个中间者就是事件中心,协调发布者和订阅者直接的消息通信。
完成订阅发布整个流程需要三个角色:
- 发布者
- 事件中心
- 订阅者
以事件为例,简单流程如下:
发布者 -> 事件中心 <=> 订阅者,订阅者需要向事件中心订阅指定的事件 -> 发布者向事件中心发布指定事件内容 -> 事件中心通知订阅者 -> 订阅者收到消息(可能是多个订阅者),到此完成了一次订阅发布的流程。
简单的代码实现如下:
class Event {constructor() {
// 所有 eventType 监听器回调函数(数组)this.listeners = {}}
/**
* 订阅事件
* @param {String} eventType 事件类型
* @param {Function} listener 订阅后发布动作触发的回调函数,参数为发布的数据
*/
on(eventType, listener) {if (!this.listeners[eventType]) {this.listeners[eventType] = []}
this.listeners[eventType].push(listener)
}
/**
* 发布事件
* @param {String} eventType 事件类型
* @param {Any} data 发布的内容
*/
emit(eventType, data) {const callbacks = this.listeners[eventType]
if (callbacks) {callbacks.forEach((c) => {c(data)
})
}
}
}
const event = new Event()
event.on('open', (data) => {console.log(data)
})
event.emit('open', { open: true})
Event 可以理解为事件中心,提供了订阅和发布功能。
订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。
观察者模式
观察者模式 定义了一种一对多的依赖关系,让多个 观察者 对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有 观察者 对象,使它们能够自动更新。
观察者模式我们可能比较熟悉的场景就是响应式数据,如 Vue 的响应式、Mbox 的响应式。
观察者模式有完成整个流程需要两个角色:
- 目标
- 观察者
简单流程如下:
目标 <=> 观察者,观察者观察目标(监听目标)-> 目标发生变化 -> 目标主动通知观察者。
简单的代码实现如下:
/**
* 观察监听一个对象的变化
* @param {Object} subject 观察的目标
* @param {Function} callback 目标变化触发的回调
*/
function observer(subject, callback) {
Object.defineProperty(subject, 'description', {get() {return this.data.description},
set(val) {
this.data.description = val
// 目标主动通知观察者
callback && callback(val)
},
})
}
可运行例子如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover"
/>
<title></title>
</head>
<body>
<div id="app">
<div id="dom-one">
原来的值
</div>
<br />
<div id="dom-two">
原来的值
</div>
<br />
<button id="btn"> 改变 </button>
</div>
<script>
/**
* 观察监听一个对象的变化
* @param {Object} subject 观察的目标
* @param {Function} callback 目标变化触发的回调
*/
function observer(subject, callback) {
Object.defineProperty(subject, 'description', {get() {return this.data.description},
set(val) {
this.data.description = val
// 目标主动通知观察者
callback && callback(val)
},
})
}
const obj = {data: { description: ''},
}
observer(obj, value => {document.querySelector('#dom-one').innerHTML = value
document.querySelector('#dom-two').innerHTML = value
})
btn.onclick = () => {obj.description = '改变了'}
</script>
</body>
</html>
两者的区别在哪?
角色角度来看,订阅发布模式需要三种角色,发布者、事件中心和订阅者。二观察者模式需要两种角色,目标和观察者,无事件中心负责通信。
从耦合度上来看,订阅发布模式是一个事件中心调度模式,订阅者和发布者是没有直接关联的,通过事件中心进行关联,两者是 解耦 的。而观察者模式中目标和观察者是直接关联的,耦合在一起(有些观念说观察者是解耦,解耦的是业务代码,不是目标和观察者本身)。
两者的优缺点?
优缺点都是从前端角度来看的。
订阅发布模式优点
- 灵活
由于订阅发布模式的发布者和订阅者是 解耦 的,只要引入订阅发布模式的事件中心,无论在何处都可以发布订阅。同时订阅发布者相互之间不影响。
订阅发布模式在使用不当的情况下,容易造成数据流混乱,所以才有了 React 提出的单项数据流思想,就是为了解决数据流混乱的问题。
订阅发布模式缺点
- 容易导致代码不好维护
灵活是有点,同时也是缺点,使用不当就会造成数据流混乱,导致代码不好维护。
- 性能消耗更大
订阅发布模式需要维护事件列队,订阅的事件越多,内存消耗越大。
观察者模式优点
- 响应式
目标变化就会通知观察者,这是观察者最大的有点,也是因为这个优点,观察者模式在前端才会这么出名。
观察者模式缺点
- 不灵活
相比订阅发布模式,由于目标和观察者是耦合在一起的,所以观察者模式需要同时引入目标和观察者才能达到响应式的效果。而订阅发布模式只需要引入事件中心,订阅者和发布者可以不再一处(同一个页面)。
参考文章
订阅发布模式和观察者模式真的不一样