什么是观察者模式?
一个或多个观察者对目标的状态感兴趣,它们通过将自己依附在目标对象上以便注册所感兴趣的内容。
目标状态发生改变并且观察者可能对这些改变感兴趣,就会发送一个通知消息,调用每个观察这的更新方法。
当观察者不再对目标状态感兴趣时,他们可以简单地将自己从中分离。
看完后你会觉得这是什么破玩意?不要急,我们举个现实中的例子,来按段落顺序逐步分解下上面这段话的意思。
去饭馆吃饭,点完餐会给你一个号码,然后就和其余点完餐的人一样,坐着等叫号。
每当做好一份餐,服务员就会喊多少号的餐好了。然后每个人都会收到这个消息,并开始检查自己手里的号,看是不是自己的餐好了,如果是自己的餐,就不要坐着了,赶紧去吃饭了。
有些人等着等着,觉得不想吃了,可能是嫌做饭太慢气饱了,就离开不吃了。
抽象组件
我们把上面这个场景再简化抽象成 JS 描述。
比如,一个 JS 对象 O 被修改了,那么它就需要自动通知那些依赖它的对象。这里所有依赖对象 O 的对象都是观察者,而对象 O 就是一个目标。
根据这个图,我们就可以假设对象 O 都是通过目标类(Subject Class)实例化出来的,而依赖对象 O 的那些对象都是通过观察者类(Observer Class)实例化出来的。
那么我们就可以抽象出来 4 个组件:
-
目标(Subject)
维护一系列的观察者,方便添加或删除观察者。对应一个目标类,提供一些注册和通知观察者的接口。 -
观察者(Observer)
在目标状态发生改变时,为需要得到通知的对象提供一个更新接口。对应一个观察者类,提供一个更新观察者状态的接口。 -
具体目标(ConcreteSubject)
状态发生改变时,向 Observer 发出通知,存储 ConcreteObserver 的状态。对应一个目标实例。 -
具体观察者(ConcreteObserver)
存储一个指向 ConcreteSubject 的引用,实现 Observer 类的更新接口,以是自身状态与目标状态保持一致。对应一个观察者实例。
实现
1、为了管理观察者,我们先实现一个 ObserverList 类。也就是一个数组结构,并提供一些常用的数组操作接口。
class ObserverList {
constructor() {
this.observerList = []
}
add(obj) {
return this.observerList.push(obj)
}
get(index) {
if (index > -1 && index < this.observerList.length) {
return this.observerList[index]
}
}
count() {
return this.observerList.length
}
remove(index) {
this.observerList.splice(index, 1)
}
indexOf(obj) {
return this.observerList.indexOf(obj)
}
}
2、目标类。提供注册和通知观察者等接口。
class Subject {
constructor() {
this.observers = new ObserverList()
}
addObserver(observer) {
this.observers.add(observer)
}
removeObserver(observer) {
let index = this.observers.indexOf(observer)
if (index >= 0) {
this.observers.remove(index)
}
}
notify(context) {
const observerCount = this.observers.count()
for (let i = 0; i < observerCount; i++) {
this.observers.get(i).update(context)
}
}
}
3、Observer 类。提供一个观察者更新接口。
class Observer {
update() {
// ...
}
}
以上便是观察者模式的实现方式。
使用
下面我们来看下观察模式的使用,我们继续前面饭馆吃饭的场景。
<button id="callClient">Call</button>
// 先实例化一个饭馆
const restaurant = new Subject()
// 实例化两个顾客 a 和 b
const a = new Observer()
const b = new Observer()
// 定义当前被叫号的顾客
let currentObserver
/**
* @desc a 顾客实现自己的更新方法
* @params restaurant 饭堂实例
*/
a.update = function(restaurant) {
// 判断当前的叫号是不是自己
if (restaurant.observers.get(0) == a) {
console.log('我是a,我的饭好了')
// 如果是自己,把当前叫号的顾客赋值成自己
currentObserver = a
}
}
// a 顾客实现自己的更新方法
b.update = function(restaurant) {
// 判断当前的叫号是不是自己
if (restaurant.observers.get(0) == b) {
console.log('我是b,我的饭好了')
// 如果是自己,把当前叫号的顾客赋值成自己
currentObserver = b
}
}
// 两位顾客先后在饭堂点餐,把自己注册为观察者
restaurant.addObserver(a)
restaurant.addObserver(b)
// 绑定通知,每次点击通知所有顾客,餐好了
callClient.onclick = function() {
// 饭好了,通知所有顾客
restaurant.notify(restaurant)
// 把拿走饭的顾客,移除,不需要关心了
restaurant.removeObserver(currentObserver)
}
以上就是一个模拟场景,应用了观察者模式。如果有不正确的地方,望指出。