观察者模式和发布订阅模式是平常业务开发中最常见的设计模式,虽然网上大多数文章将二者归为一类,其实不然,它们两者之间还是有细微的差距。
先来个观察者模式的定义
- 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的某个属性(或状态)发生变化时,会通知所有观察者对象,让它们自动更新。
现实映射
- 举一个🌰,高中的时候,我会经常去问老师问题,有时候遇到比较难的问题,老师一时半会解不开,老师会说**“你先去做其他的事情吧,一会儿我找到解题思路了来叫你”**。于是乎我先去做其他事情,等待老师的召唤。过了一会儿,老师叫另一个同学来叫我去办公室找他,于是我马上放下手中的活,冲向的老师的办公室……
- 在这里例子里,我是一位观察者,而老师则是一位我观察的对象,当老师的状态发生了变化(指想出了题的思路),我就会接受到对应的信息,然后马上更新我自己的状态(指润去找老师)。
来点转换
- 上述例子如果在
发布-订阅
模式里,我则摇身一变,变成了订阅者,专门订阅老师发布的通知信息,而老师则作为了发布者。 - 其实,上述例子还不能完全展示出定义所说的一对多关系,因为订阅者只有我一个人,但其实稍微扩展一下,变成多位同学向老师询问同一道题目,那这就是标准的观察者模式了,多位观察者“观察”老师的状态。
来点代码
- 通过上述的定义和描述,大概可以知道,在观察者模式中,一共有两个类:发布者类和订阅者类。作为一个发布者,很容易可以想到它有下面几个基本方法:增加订阅者,通知订阅者,移除订阅者。思路有了,下面就直接实现。
// 发布者类
class Publisher {
constructor() {
this.observers = [] // Observer -> 观察员
}
// 添加订阅者
add(observers) {
this.observers.push(...observers)
}
// 移除订阅者
remove(observer) {
this.observers.forEach((item, index) => {
if (item === observer) {
this.observer.splice(index, 1)
}
})
}
// 通知订阅者
notify() {
this.observers.forEach((item) => {
item.update(); // 注意,订阅者的方法应该它们本身定义的
})
}
}
- 发布者基本类设计完毕,下面开始设计下订阅者,其实订阅者很简单,它最核心的就一个方法:收到发布者的信息后,去进行状态更新。如下所示。
// 定义订阅者类
class Observer {
constructor() {
console.log('创建订阅者')
}
update() {
console.log('更新状态')
}
}
- 好了,上述两段代码就是最基本的观察者模式实现,实际场景的观察者模式都是基于上面的代码进行迭代。比如下面我将会把之前说的实例进行代码实现
// 教师类
class Teachere extends Publisher {
constructor() {
super()
//
this.answers = null
this.observers = []
}
getAnswers() {
return this.answers
}
setAnswers(ques) {
this.answers = answers;
console.log('老师想出了答案,快来办公室找老师')
this.notify()
}
notify() {
const that = this;
this.observers.forEach(item => {
item.update(that)
})
}
}
// 学生类
class Student extends Observer {
constructor() {
console.log("我是学生")
}
update(info) {
const answer = info.getAnswers()
// ... 进行相关的更新操作
}
}
观察者模式和发布-订阅模式的区别
- 其实在刚才的例子不算严格意义上的
发布-订阅
模式,因为发布-订阅模式中,发布者和订阅者二者之间是透明的,即它们是彼此感受不到对方的,无论是发布事件,还是订阅事件,都是交给一个统一的管理系统来进行处理的,这就类比于游戏原神中的每日委托一样,委托者在冒险家协会进行每日任务的委托,而旅行者-我则会去冒险家协会去领取每日任务,这个过程中,我们彼此感受不到对方,而是全权交给冒险家协会来进行管理的。 - 如果放到Javascript世界中,无论是Vue的EventBus还是Nodejs中的EventEmitter类,它们的设计理念就是发布-订阅模式,即发布事件 - 订阅事件这一个过程。
// 订阅事件
event.on("callTeacher", function(...args){
})
// 发布事件
event.emit('callTeacher', arg1, arg2, arg3, ...args)
- 很明显,和观察者模式不同的是,发布-订阅模式则将订阅者和发布者完全解耦,二者再也没有直接关联,它们的一切处理都统一交给第三方处理。
- 下面是一个简单的EventEmitter实现,为了加深对发布-订阅模式的认识。
// 发布-订阅模式的管理站
class EventEmitter {
constructor() {
// handlers -> map,存储事件和回调之间的关系
this.handlers = {}
}
// eventname -> 事件名称 , cb -> 响应的回调函数
on(eventName, cb) {
if (!this.handlers[eventName]) {
this.handlers[eventName] = []
}
this.handlers[eventName].push(cb);
}
emit(eventName, ...args) {
if (this.handlers[eventName]) {
// 这里用拷贝,否则once的删除会导致handlers的forEach遍历失序
const handlers = this.handlers[eventName].slice()
handlers[eventName].forEach(cb => {
cb(...args)
})
}
}
off(eventName, cb) {
if (this.handlers[eventName]) {
this.handlers[eventName].forEach((item, index) => {
if (item === cb) {
this.handlers[eventName].splice(index, 1)
}
})
}
}
// 为事件注册单次监听器
once(eventName, cb) {
const wrapper = (...args) => {
cb(...args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
}
}