今天在面试中被问道, “观察者模式和发布订阅模式的有什么区别?” "哼,这不是一样的东西吗",我内心暗喜,又a了一个八股回答😄😄。
发布 + 订阅 = 观察者模式
‘没有区别,他们是一样的。’
面试官微笑😂:'你在好好想想。'
😡😡难道是我错误了吗?回到寝室查阅笔记文档以及技术博客,下面这边文章是对观察者模式和发布订阅模式的总结
前言
观察者模式&&发布订阅模式都是前端常见的设计模式,可是他们到底有区别吗?或者有区别在哪?两种模式的优缺点在哪?分别应用场景是什么?接下来在下面一一道来。
发布-订阅模式
发布订阅模式是对象间的一种
一对多的关系,发布者、订阅者、消息管理器三部分组成,让多个观察者对象同时[监听]某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
现实生活中的发布-订阅模式;
比如小红最近最近在apple专卖店上想买iphone14,但是呢 联系到店员后,才发现iPhone14缺货,但是小红又非常喜欢iPhone14,所以询问店员,问店员什么时候有货,卖家告诉她,要等一个星期后才有货,卖家告诉小红,要是你喜欢的话,你可以在笔记本登记下你的信息,等有货的时候再通知你,所以小红登记了自己的练习方式,但与此同时,小明,小花等也喜欢iphone14,也登记了练习方式;等来货的时候就依次会通知他们;
在上面的故事中,可以看出是一个典型的发布订阅模式,发布者是店员,订阅者是小红小花等顾客,消息管理则是小花等顾客留下联系方式。
如何实现发布–订阅模式
- 首先要想好谁是发布者(比如上面的卖家)。
- 然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者(比如上面的顾客留下了练习方式,店员通过列表联系名单)。
- 最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数。
class EventEmitter {
constructor() {
// 维护事件及监听者
this.listeners = {}
}
/**
* 注册事件监听者
* @param {String} event 事件类型
* @param {Function} cb 回调函数
*/
on(type, cb) {
if (!this.listeners[type]) {
this.listeners[type] = []
}
this.listeners[type].push(cb)
// console.log(this.listeners);
}
/**
* 发布事件
* @param {String} type 事件类型
* @param {...any} args 参数列表,把emit传递的参数赋给回调函数
*/
emit(type, ...args) {
if (this.listeners[type]) {
this.listeners[type].forEach(cb => {
cb(...args)
})
}
}
/**
* 移除某个事件的一个监听者
* @param {String} type 事件类型
* @param {Function} cb 回调函数
*/
off(type, cb) {
if (this.listeners[type]) {
const targetIndex = this.listeners[type].findIndex(item => item === cb)
if (targetIndex !== -1) {
this.listeners[type].splice(targetIndex, 1)
}
if (this.listeners[type].length === 0) {
delete this.listeners[type]
}
}
}
once(type,cb) {
this.on(type,cb)
setInterval(() => {
this.off(type,cb)
}, 0);
}
/**
* 移除某个事件的所有监听者
* @param {String} type 事件类型
*/
offAll(type) {
if (this.listeners[type]) {
delete this.listeners[type]
}
}
}
// 创建事件管理器实例
const ss = new EventEmitter()
// 注册一个chifan事件监听者(订阅者--小花--小红。。。)
ss.on('shopping', function(name) { console.log(`${name}小花手机有货了,快来抢购!!!`)})
ss.on('shopping', function(name) { console.log(`${name}小红手机有货了,快来抢购!!!`)})
ss.on('shopping', function(name) { console.log(`${name}小明手机有货了,快来抢购!!!`)})
// 发布事件shopping (发布者---店员)
ss.emit('shopping','顾客')
// 测试移除事件监听
const toBeRemovedListener = function() { console.log('我是一个可以被移除的监听者') }
ee.once('testoff', toBeRemovedListener)
ee.emit('testoff')
// ee.off('testoff', toBeRemovedListener)
setInterval(() => {
ee.emit('testoff')
},1000 ); // 此时事件监听已经被移除,不会再有console.log打印出来了
// // 测试移除chifan的所有事件监听
// ee.offAll('chifan')
// console.log(ee) // 此时可以看到ee.listeners已经变成空对象了,再emit发送chifan事件也不会有反应了
优点
松耦合(Independence)
发布-订阅模式可以将众多需要通信的子系统(Subsystem)解耦,每个子系统独立管理。而且即使部分子系统取消订阅,也不会影响事件总线的整体管理。 发布-订阅模式中每个应用程序都可以专注于其核心功能,而事件总线负责将消息路由到每个订阅者手里。
高伸缩性(Scalability)
发布-订阅模式增加了系统的可伸缩性,提高了发布者的响应能力。原因是发布者(Publisher)可以快速地向输入通道发送一条消息,然后返回到其核心处理职责,而不必等待子系统处理完成。然后事件总线负责确保把消息传递到每个订阅者(Subscriber)手里。
高可靠性(Reliability)
发布-订阅模式提高了可靠性。异步的消息传递有助于应用程序在增加的负载下继续平稳运行,并且可以更有效地处理间歇性故障。
灵活性(Flexibility)
你不需要关心不同的组件是如何组合在一起的,只要他们共同遵守一份协议即可。 发布-订阅模式允许延迟处理或者按计划的处理。例如当系统负载大的时候,订阅者可以等到非高峰时间才接收消息,或者根据特定的计划处理消息。
缺点
- 在创建订阅者本身会消耗内存,但当订阅消息后,没有进行发布,而订阅者会一直保存在内存中,占用内存;
- 创建订阅者需要消耗一定的时间和内存。如果过度使用的话,反而使代码不好理解及代码不好维护。
使用场景
如果我们项目中很少使用到订阅者,或者与子系统实时交互较少,则不适合 发布-订阅模式 。 在以下情况下可以考虑使用此模式:
- 应用程序需要向大量消费者广播信息。例如微信订阅号就是一个消费者量庞大的广播平台。
- 应用程序需要与一个或多个独立开发的应用程序或服务通信,这些应用程序或服务可能使用不同的平台、编程语言和通信协议。
- 应用程序可以向消费者发送信息,而不需要消费者的实时响应。
观察者模式
观察者模式是当对象之间存在一对多的依赖关系时,其中被观察对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式。观察者模式和发布订阅模式有点很相似,但是其实有所不同的。
观察者模式中被观察者(主体)维护观察者列表,因此主体知道当状态发生变化时如何通知观察者。然而,在发布订阅模式中,发布者和订阅者不需要相互了解。它们只需在事件调度中心(或消息队列)的帮助下进行通信。
class Subject {
constructor() {
this.observers = []
}
// 增加观察者
add(observer) {
this.observers.push(observer)
}
// 移除观察者
remove(observer) {
this.observers.forEach((item, i) => {
if (item === observer) {
this.observers.splice(i, 1)
}
})
}
// 通知所有观察者
notify(message) {
this.observers.forEach((observer) => {
observer.update(message)
})
}
}
// 定义观察者类
class Observer {
constructor(name) {
this.name = name
}
update(message) {
console.log('我是'+this.name+',我收到通知了'+message);
}
}
const beOservered = new Subject()
const person1 = new Observer('red')
const person2 = new Observer('jack')
beOservered.add(person1)
beOservered.add(person2)
beOservered.notify('这是主题传递的信息1')//我是red我收到通知了这是主题传递的信息1 我是jack我收到通知了这是主题传递的信息1
//移除person1观察者
beOservered.remove(person1)
beOservered.notify('主题传递的信息2')//我是red我收到通知了这是主题传递的信息2
发布订阅&&观察者模式异同
- 在观察者模式中,主体维护观察者列表,因此主体知道当状态发生变化时如何通知观察者。然而,在发布者/订阅者中,发布者和订阅者不需要相互了解。它们只需在中间层消息代理(或消息队列)的帮助下进行通信。
- 在发布者/订阅者模式中,组件与观察者模式完全分离。在观察者模式中,主题和观察者松散耦合。
- 观察者模式主要是以同步方式实现的,即当发生某些事件时,主题调用其所有观察者的适当方法。发布服务器/订阅服务器模式主要以异步方式实现(使用消息队列)。
- 发布者/订阅者模式更像是一种跨应用程序模式。发布服务器和订阅服务器可以驻留在两个不同的应用程序中。它们中的每一个都通过消息代理或消息队列进行通信。
以下是我自己浅薄的观点,我认为观察者和发布订阅的本质是相同的,发布订阅模式在观察者模式
观察者和主体的功能上做了解耦,让发布者和订阅者不在相联系,用一个事件调度中心来联系发布者和订阅者,起到了松耦合的作用。观察者模式主要是以同步方式实现,而发布订阅则更像是异步通信。
此文章学习借鉴了设计模式之发布订阅模式(1) 一文搞懂发布订阅模式加入自己理解,希望各位大佬指正。