前言
观察者模式
定义了一种一(被观察者)对多(观察者)
的关系。被观察者
发生改变就会通知所有的观察者
。它应用广泛,就像常用的addEventListener
发布订阅模式
从广义上来说也是观察者模式
,就如《JS设计模式与开发实践》
所言:分辨模式的关键是意图而不是结构
,结构虽不一样,意图却是一样
正文
实现方法,方法命名很多很多,随意实现即可
观察者模式
- 结构
╭─────────────╮ Fire Event ╭──────────────╮
│ │─────────────>│ │
│ Subject │ │ Observer │
│ │<─────────────│ │
╰─────────────╯ Subscribe ╰──────────────╯
- 比喻
你去一个数码店买鼠标,恰好没有你要的牧马人,你就和店主(目标)
说有了的话通知你(订阅)
,过了俩天货到了,店主发短信(你预留的通知接口,就像实现里的update)
你说到货了(发布)
很明显,你和店主绑定,他知道你你知道他 - 实现
// 被观察者即目标
class Subject {
constructor() {
this._observes = []
}
// 订阅
add(ob) {
const { _observes } = this
const index = _observes.indexOf(ob)
if(ob && !~index) {
_observes.push(ob)
}
}
remove(ob) {
const { _observes } = this
if(ob) {
const index = _observes.indexOf(ob)
~index && _observes.splice(index, 1)
}
}
// 发布
notify(args) {
this._observes.forEach(ob => {
ob.update(args)
})
}
}
// 观察者,需要提供一个接口(update)给目标,让它可以通过这个接口传递更新的消息
class Observer {
constructor(name) {
this.name = name
}
update(...args) {
console.log(`${this.name}收到了:`, args)
}
}
// 目标告诉观察者自己更新了,所以得建个目标对象
const subject = new Subject()
// 创建观察者
const ob1 = new Observer('ob1')
const ob2 = new Observer('ob2')
subject.add(ob1)
subject.add(ob2)
subject.notify('海贼王更新到800集了')
console.error('退订了,推送新消息')
subject.remove(ob2)
subject.notify('海贼王更新到801集了')
这种实现发布者和订阅者绑定在一块了,松耦合(面向接口),如此例这些
Observer
实现了相同的接口update
,这样子Subject
就可以知道自己更新了通过什么接口通知Observer
发布订阅模式
- 结构
╭─────────────╮ ╭───────────────╮ Fire Event ╭──────────────╮
│ │ Publish Event │ │───────────────>│ │
│ Publisher │────────────────>│ Event Channel │ │ Subscriber │
│ │ │ │<───────────────│ │
╰─────────────╯ ╰───────────────╯ Subscribe ╰──────────────╯
- 比喻
你在看起点(Broker)
上看圣墟(topic)
,因为辰东(发布者)
更新不规律所以你订阅了圣墟(订阅了圣墟事件)
,一旦辰东更新了发到起点上,起点发现辰东更新了就会通知(发布)
你小说更新了
很明显,辰东不知道你是谁他只管传他的小说就是了,你也不需要知道小说是谁写的,只需要知道你看的小说叫什么,照样可以看的很开心 - 实现
// 经纪人,中间站,消息中心etc
class Broker {
constructor() {
this.subs = {}
}
// 订阅
subscribe(topic, cb) {
const { subs } = this
// 需要订阅的主题是否有人订阅过
const cbs = subs[topic]
if(cbs) {
cbs.publish(cb)
} else {
this.subs[topic] = [cb]
}
}
unsubscribe(topic, cb) {
const { subs } = this
const cbs = subs[topic]
if(cbs) {
const index = cbs.indexOf(cb)
if(~index) {
cbs.splice(index, 1)
}
}
}
// 发布
publish(topic, args) {
(this.subs[topic] || []).forEach(cb => {
cb(args)
})
}
}
const broker = new Broker()
// A订阅了A事件,他只关心A事件本身,至于谁发布的无所谓
broker.subscribe('A', function A(...args) {
console.log(`A接收到了:`, args)
})
function B(...args) {
console.log(`B接收到了:`, args)
}
// B订阅了B事件,他只关心A事件本身,至于谁发布的无所谓
broker.subscribe('B', B)
// XX发布了A事件,他只关心他发了什么,谁接收的无所谓
broker.publish('A', '海贼王更新到800集了')
// XX发布了B事件
broker.publish('B', '海贼王更新到800集了')
// B退订了B事件
broker.unsubscribe('B', B)
// XX发布了A事件
broker.publish('A', '海贼王更新到801集了')
// XX发布了B事件
broker.publish('B', '海贼王更新到801集了')
发布订阅模式里,发布者和订阅者完全解耦,互不关心,只关心消息本身即可