两年前写过一篇关于消息发布订阅的文章,当时是结合了实际的应用场景。看起来不够抽象,概括。今天试图抽象话的再去写一下自己的理解。
此文续上篇发布 订阅 消息系统(一),可点击查看,取名改为观察者和发布订阅模式。
观察者模式(Observer Pattern)定义了一种当对象之间存在一对多的关系时的一种行为模式。比如当一个对象被修改时,则会通知它的依赖对象并自动更新状态。它主要解决了一个对象状态改变给其他对象通知的问题,并且考虑到易用和低耦合度,保证高度的协作性。
比如一个应用的实例:军训时,教官(目标对象)的状态发生改变,发出“立正”的口令,则所有的学生(观察者对象)此时得到通知,状态随之更新,做出"立正“的动作响应。
该案例的特征:
//发布者
class Subject {
constructor() {
this.observers = []
}
add(observer) {
this.observers.push(observer)
}
delete(observer) {
let idx = this.observers.findIndex( item => item === observer);
idx > -1 && this.observers.splice(idx, 1)
}
notify() {
for(let observer of this.observers) {
observer.update()
}
}
}
//观察者
class Observer {
constructor(name) {
this.name= name
}
update() {
console.log(`我是${this.name},我立正了`)
}
}
// 实例化目标者
let subject = new Subject();
// 实例化两个观察者
let obs1 = new Observer('刘德华');
let obs2 = new Observer('吴彦祖');
// 向目标者添加观察者
subject.add(obs1);
subject.add(obs2);
// 目标者通知更新
subject.notify();
//打印结果
//我是刘德华,我立正了
//我是吴彦祖,我立正了
上面代码可以添加、删除观察者,当然你可以定制自己需求的功能函数。
当然这种模式是存在一些不足的,比如:
发布订阅模式: 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
var pubsub = {
//事件调度中心
topics: {},
//注册事件到调度中心
subscribe(topic, fn) {
this.topics[topic] = this.topics[topic] || []
this.topics[topic].push(fn)
},
//将事件发布到调度中心
publish(topic, ...args) {
if (!this.topics[topic]) return
for (let fn of this.topics[topic]) {
fn(...args)
}
},
//解绑到调度中心的事件
unsubScribe(topic, fn) {
let fnList = this.topics[topic]
if (!fnList) return
//若不传入指定的要取消的订阅的方法(fn),则清除所有topic下的订阅
if (!fn) {
fnList && (fnList.length == 0)
} else {
fnList.forEach((item, index) => {
if (item == fn) {
fnList.splice(index, 1)
}
})
}
}
}
//注册eat事件
pubsub.subscribe('eat', time => {
console.log(`now is ${time},time to eat lunch`);
})
//注册work事件
pubsub.subscribe('work', time => {
console.log(`now is ${time},time to work`);
})
//发布work,eat事件
pubsub.publish('work', '8:30 AM') //now is 8:30 AM,time to work
pubsub.publish('eat', '12:30 AM') //now is 12:30 AM,time to eat lunch
//取消eat事件的订阅
pubsub.unsubScribe('eat')
发布订阅模式与观察者模式的不同,在于第三者的出现:事件中心(topic)的出现。目标对象并不直接通知观察者,而是通过事件中心来派发事件。订阅者只关心自己订阅事件以此来响应变化。
就像上文说的那样,DOM的监听事件其实也是该模式的应用。比如jQuery的onclick事件,one()只触发一次响应事件,off()解除绑定事件(类比上面代码的unsubScribe事件)
借用一张图表示如下:
借用一个同学的总结:
网上关于这个问题的回答,出现了两极分化,有认为发布订阅模式就是观察者模式的,也有认为观察者模式和发布订阅模式是真不一样的。
其实我不知道发布订阅模式是不是观察者模式,就像我不知道辨别模式的关键是设计意图还是设计结构(理念),虽然《JavaScript设计模式与开发实践》一书中说了分辨模式的关键是意图而不是结构
。
如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新
,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。
不过,不管他们是不是同一个设计模式,他们的实现方式确实有差别,我们在使用的时候应该根据场景来判断选择哪个。
此文没有长篇大论,力争以最简的话语表述我所理解的内容。其中借鉴了以下三位的同学的一些理解。如果看完还是云里雾里,可以去看下他们的文章,写的很不错。
发布订阅模式与观察者模式 https://segmentfault.com/a/1190000018706349 作者:hfhan
重学JS(九)—— 观察者模式和发布/订阅模式真不一样:https://www.jianshu.com/p/f0f22398d25d 作者:闪闪发光的狼
JavaScript 设计模式(六):观察者模式与发布订阅模式 https://segmentfault.com/a/1190000019722065 作者: 以乐之名