行为型:观察者模式
区别
虽然观察者模式经常又被叫做发布订阅模式,但是二者还是有区别的。
观察者模式是被观察者(Subject)直接通知所有的观察者(Observer)。被观察者和观察者之间是松耦合的。

发布订阅模式中,发布者(Publisher)并不直接通知所有的订阅者(Subscriber),而是通过第三方的EventChannel。发布者和订阅者之间是完全解耦的。

实现
观察者模式
首先我们来实现Observer,这个类我们只需要一个update方法用来供Subject通知调用:
interface Observer {
update(subject: Subject): void
}
class ObserverA implements Observer {
public update(subject: Subject) {
console.log('ObserverA updated')
}
}
class ObserverB implements Observer {
public update(subject: Subject) {
console.log('ObserverB updated')
}
}
接下来实现Subject,这个类需要实现add(添加观察者)、remove(移除观察者)和notify(通知观察者)三个方法,同时用observer私有属性来维护所有的观察者集合。
interface Subject {
add(observer: Observer): void
remove(observer: Observer): void
notify(): void
}
class SubjectA implements Subject {
private observers: Observer[]
public constructor() {
this.observers = []
}
public add(observer: Observer) {
this.observers.push(observer)
}
public remove(observer: Observer) {
const index = this.observers.findIndex(o => o === observer)
this.observers.splice(index, 1)
}
public notify() {
this.observers.forEach(observer => observer.update(this))
}
}
我们注意到在notify()方法调用中调用遍历调用每一个observer的update()方法时,会传递this,即把当前的subject实例传递过去。为什么需要这样做呢?因为通常Subject的业务类都会包含一些业务逻辑,才能实现我们实际业务中格中丰富的功能。
想像这样一个场景:小明(ObserverA)和小红(ObserverB)分别喜欢阅读某个小说平台(StorySubject)里的小说A和小说B。小说平台里所有小说更新的时候都会给关注的用户推送私信,但是小明和小红都只关心自己喜欢的小说什么时候更新。
我们目前的实现,notify()调用时,会无差别调用所有observer的update()方法。但是上面的场景中小明和小红都只关心各自喜欢的小说的更新情况。那么在StorySubject中引入额外的状态就可以通过状态判断来做到这一点了。我们来改造StorySubject:
class StorySubject implements Subject {
/**
* 业务状态,0代表A小说更新了,1代表B小说更新了
*/
public state = 0
/**
* 业务逻辑,模拟状态更新,通知观察者
*/
public changeState() {
this.state = Math.random() > 0.5 ? 1 : 0
this.notify()
}
}
改造Observer类:
class ObserverA implements Observer {
public update(subject: Subject) {
if (subject && subject instanceof StorySubject && subject.state === 1) {
console.log('A小说更新了')
}
}
}
class ObserverB implements Observer {
public update(subject: Subject) {
if (subject && subject instanceof StorySubject && subject.state === 0) {
console.log('B小说更新了')
}
}
}
通过对subject.state的判断,小明和和小红都能知道自己喜欢的小说何时更新了:
const observerA = new ObserverA() // 小明
const observerB = new ObserverB() // 小红
const storySubject = new StorySubject()
storySubject.add(observerA)
storySubject.add(observerB)
storySubject.changeState() // A/B小说更新了(取决于当前state的值)
发布订阅模式
发布订阅模式需要实现三个类:Publisher、Subscriber和EventChannel:
type Callback = (...args: unknown[]) => void
// 发布者
interface Publisher {
eventName: string
data: any
}
// 订阅者
interface Subscriber {
eventName: string
callback: Callback
}
// 事件总线
interface EventChannel {
on: (eventName: string, callback: Callback) => void
off: (eventName: string, callback: Callback) => void
emit: (eventName: string, data: any) => void
}
分别实现它们:
class ConcretePublisher implements Publisher {
public eventName: string
public data: any
constructor(eventName: string, data: any) {
this.eventName = eventName
this.data = data
}
}
class ConcreteSubscriber implements Subscriber {
public eventName: string
public callback: Callback
constructor(eventName: string, callback: Callback) {
this.eventName = eventName
this.callback = callback
}
}
class ConcreteEventChannel implements EventChannel {
private handlers: Map<string, Callback[]>
constructor() {
this.handlers = new Map()
}
public on(eventName: string, callback: Callback) {
if (!this.handlers.has(eventName)) {
this.handlers.set(eventName, [])
}
this.handlers.get(eventName)!.push(callback)
}
public off(eventName: string, callback: Callback) {
const callbacks = this.handlers.get(eventName)
if (callbacks) {
const index = callbacks.indexOf(callback)
if (index !== -1) {
callbacks.splice(index, 1)
}
}
}
public emit (eventName: string, ...args: unknown[]) {
const callbacks = this.handlers.get(eventName)
if (callbacks) {
callbacks.forEach((callback) => {
callback(...args)
})
}
}
}
用例:
const sub1 = new ConcreteSubscriber('ready', (...args) => {
console.log('sub1接收到ready事件', args)
})
const sub2 = new ConcreteSubscriber('ready', (...args) => {
console.log('sub2接收到ready事件', args)
})
const pub1 = new ConcretePublisher('ready', {
message: 'pub1',
data: 1
})
const pub2 = new ConcretePublisher('ready', {
message: 'pub2',
data: 2
})
const eventChannel = new ConcreteEventChannel()
eventChannel.on(sub1.eventName, sub1.callback)
eventChannel.on(sub2.eventName, sub2.callback)
eventChannel.emit(pub1.eventName, pub1.data)
eventChannel.off(sub2.eventName, sub2.callback)
eventChannel.emit(pub2.eventName, pub2.data)
/**
* [LOG]: "sub1接收到ready事件", [{"message": "pub1","data": 1}]
* [LOG]: "sub2接收到ready事件", [{"message": "pub1","data": 1}]
* [LOG]: "sub1接收到ready事件", [{"message": "pub2","data": 2}]
*/
发布订阅模式的核心在于EventChannel,所以很多实现中可以不需要Publisher类和Subscriber类。比如上面的用例,如果只有EventChannel类,功能也是一样的:
const eventChannel = new ConcreteEventChannel()
function callback1(...args) {
console.log('sub1接收到ready事件', args)
}
function callback2(...args) {
console.log('sub2接收到ready事件', args)
}
eventChannel.on('ready', callback1)
eventChannel.on('ready', callback2)
eventChannel.emit('ready', {
message: 'pub1',
data: 1
})
eventChannel.off('ready', callback2)
eventChannel.emit('ready', {
message: 'pub2',
data: 2
})
比如我们常见的EventEmitter的实现就是这样,可以参考我的这篇文章。