一、发布订阅模式
在软件架构是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
发布/订阅是消息队列范式的兄弟,通常是更大的面向消息中间件系统的一部分。大多数消息系统在API中同时支持消息队列模型和发布/订阅模型。
const fs = require('fs')
// 消息的调度控制中心
const eventCenter = {
messageList: {},
on: (key, value) => {
this.messageList[key] = value
},
emit: () => {
if(Object.keys(this.messageList).length === 2){
console.log(this.messageList)
}
}
}
// js读取两个文件的数据
fs.readFile('./name.txt','utf8', (err,data)=> {
eventCenter.on('name', data) // 通知数据中心,读取到数据了
eventCenter.emit() // 向数据中心发布事件
})
fs.readFile('./age.txt','utf8', (err,data)=> {
eventCenter.on('age', data) // 通知数据中心,读取到数据了
eventCenter.emit() // 向数据中心发布事件
})
换个方式(通常以事件的形式,采用这种):
const fs = require('fs')
// 消息的调度控制中心
const eventCenter = {
messageList: [], // 发布和订阅没有任何关系
on: (event) => { // 订阅事件
this.messageList.push(event)
},
emit: () => { // 发布执行事件
this.messageList.forEach(event => event())
}
}
const readData = {};
// 订阅事件1
eventCenter.on(() => {
console.log('读取到了数据')
})
// 订阅事件2
eventCenter.on(() => {
if(Object.keys(readData).length === 2){
console.log('数据读取完毕!', readData)
}
})
// js读取两个文件的数据
fs.readFile('./name.txt','utf8', (err,data)=> {
readData.name = data
eventCenter.emit() // 向数据中心发布事件
})
fs.readFile('./age.txt','utf8', (err,data)=> {
readData.age = data
eventCenter.emit() // 向数据中心发布事件
})
看图说人话,发布订阅模式里,不仅仅只有发布者和订阅者两个角色,还有一个经常被忽略的经纪人Broker(调度中心)。个人理解发布订阅模式是两类对象之间通讯的一种机制,消息的发送者将消息发送到一个消息调度中心,存起来,让后让发布者取执行对应的事件,就是说发布者和订阅者都会去操作调度中心的数据。 订阅就是将要做的事情先存储好,发布的时候就让订阅的事情依次执行。
二、观察者模式
观察者模式(有时又被称为模型(Model)-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种,是一种对象行为的模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。
观察者模式(Observer)完美的将观察者和被观察的对象分离开,定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
// 被观察者
class Subject {
constructor(name, status){
this.name = name // 被观察者的名称
this.status = status // 被观察者的状态
this.observerList = []
}
// 注册观察者,基于发布订阅
attach(observer){
this.observerList.push(observer)
}
// 小宝宝更新状态
publish(newStatus){
this.status = newStatus
console.log(`当前被观察者${this.name}的状态改变了:${this.status}`)
// 同时通知所有观察者,执行观察者在被观察者状态变化之后要做的事情
this.observerList.forEach(observer => observer.update(this))
}
}
// 观察者
class Observer {
constructor(name){
this.name = name // 观察者的名称
}
update(subject){
// 在被观察者的状态/消息发生变动的时,执行的时间。也可以传入各个观察者自己的处理逻辑函数
console.log(`${this.name}知道${subject.name}有情况了:${subject.status}`)
}
}
// 实例化一个被观察者son 和 两个观察者(father,mather),
// 两个观察者随时观察着被观察者son的状态
const son = new Subject('小宝宝', '开心😄')
const father = new Observer('爸爸👨')
const mather = new Observer('妈妈👩')
console.log(`当前被观察者${son.name}的状态:${son.status}`)
// 注册观察关系
son.attach(father)
son.attach(mather)
// 被观察者状态变更,会依次通知所有观察着
son.publish('我要哭了😢')
看图说人话,观察者模式中,只有两个角色:观察者 和 被观察者。个人理解被观察者有一个类似订阅的功能,将自身和需要观察自己的观察者关联,就是将观察自己的观察者添加到自己的观察着队列中,在自己的状态/消息发生变动的时候,依次遍历执行观察者队列中观察者绑定的事件,并传入自己的最新数据和信息。
三、发布订阅和观察者模式的对比
发布订阅模式:发布者和订阅者之间没有任何联系,通过调度中心来做中间的数据信息交换。
观察者模式:观察者和被观察者之间有绑定的关系,内部基于发布订阅模式的,将观察者订阅到对应的被观察者身上,在被观察者有状态/消息发生变动的时,发布给所有订阅过的观察中。
虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。