什么是观察者模式
一个或多个观察者(Observer)对目标(Subject)的状态感兴趣,它们通过将自己依附(Add(Observer))在目标(Subject)对象上以便注册所感兴趣的内容。目标(Subject)状态发生改变并且观察者可能对这些改变感兴趣时,就会发送(Notify)一个通知消息,调用每个观察者(Observer)的更新方法(update)。当观察者不再对目标感兴趣时,它们可以简单的将自己从中分离(Remove(Observer))。
从上面的定义中,我们可以抽象出几个概念
- 目标(Subject)
- 观察者(Observer)
- 注册事件(Subject.Add(Observer))
- 取消注册事件(Subject.Remove(Observer))
- 发送通知事件(Subject.Notify)
- 更新观察者事件(Observer.update)
我们可以用以下的类图来描述观察者模式
观察者模式的简单实现
Subject
function Subject () {
// 用于存储全部的observer
this.observers = []
}
Subject.prototype.Add = function (observer) {
this.observers.push(observer)
}
Subject.prototype.Remove = function (observer) {
this.observers.splice(this.observers.indexOf(observer), 1)
}
Subject.prototype.Notify = function (context) {
this.observers.forEach(function (observer) {
observer.update(context)
})
}
Observer
function Observer () {
this.update = function () {
// observer需要具体实现自己的update方法
}
}
由于每一个Observer都是不一样的,所以这里我们提供一个extendObserver方法,目的是把一些普通对象改造成Observer对象(也可以用类继承的方式实现,这里只是简化处理)
function extendObserver (obj) {
var observerInstance = new Observer()
for (var key in obj) {
// 这样可以保证传入的obj中的update方法可以覆盖Observer中的update方法
observerInstance[key] = obj[key]
}
return observerInstance
}
业务场景模拟
假设我们有这样一个场景,有一个输入框a,另外有两个div b和c。b和c想要监听到输入框A的onchange事件,并执行自己的处理,b显示a的值,c显示’hello:’+a。
var elA = document.getElementById('a')
var elB = document.getElementById('b')
var elC = document.getElementById('c')
// 实例化 subject和observer对象
var subjectA = new Subject()
elB.update = function (context) {
elB.innerHTML = context
}
elC.update = function (context) {
elC.innerHTML = 'hello:' + context
}
var observerB = extendObserver(elB)
var observerC = extendObserver(elC)
// 注册监听
subjectA.Add(observerB)
subjectA.Add(observerC)
elA.onchange = function () {
// 如果不通观察者实现,则需要把update方法里的所有内容在这里写一遍,但实际A并不关心B和C都需要做出哪些响应
subjectA.Notify(elA.value)
}
结合上面的例子,观察者模式的优点就在与,在onchange事件中,我不必去关注观察者监听后的具体实现,将具体的实现进行解藕,交给观察者自己维护
具体里子可以参见https://github.com/yonyou-xtech/book-code/blob/master/设计模式/Observer.html
观察者模式和发布订阅模式的区别
大多数人会将观察者模式和发布订阅模式混淆在一起,但实际上这两者很相似但还是有一定的差异的。
发布/订阅模式使用了一个事件通道/调度中心来避免订阅者和发布者产生依赖关系。观察者则由具体的目标(Subject)调用观察者的update方法进行调度。发布/订阅模式更常用于全局的事件总线,如nodejs中的eventEmit。
观察者模式的实际场景的应用
- Vuejs的双向数据绑定(具体可以参考【前端框架】->【Vuejs】vue响应式原理章节)
- Rxjs
- Mobx