一、什么是观察者模式
观察者模式,引用维基百科的说法,一个目标对象管理所有相依与它的观察者对象,并在它本身状态改变时发生通知。这通常通过调用各观察者所提供的方法来实现。在前端应用的范围还挺多,最常见的事件监听,addEventListener,我们在一个元素上注册了各类事件,而当对象发生相应改变时,通过调用绑定的事件告诉观察者。就相当于在一个对象上绑定了各类观察者。
举个与前端不相关的例子,小偷警察的故事。小偷数量就是我们这里的目标对象,有三个警察负责看管(观察者),当小偷数量大于3了,一个警察要去报告上级,一个警察要去警告市民引起重视,一个去和小偷谈话。在程序里通过调用这三个警察的方法来相当于告诉这三个警察小偷数量大于3了。
二、用代码看观察者模式思路
/*
被观察类
addObserver()方法把观察者对象添加到观察者对象列表中
setChange()方法用来设置一个内部标志位注明数据发生了变化
notifyObservers()方法会去调用观察者对象列表中所有的Observer的update()方法,通知它们数据发生了变化。
*/
function Observable(){
this.observerArray = new Set();
this.updateFlage = false;
}
Observable.prototype.addObserver = function(data){
this.observerArray.add(data);
}
Observable.prototype.setChange = function(){
this.updateFlage = true;
}
Observable.prototype.notifyObservers = function(data){
if(this.updateFlage){
for(let item of this.observerArray){
item.update(data)
}
}
this.updateFlage = false
}
/*
观察者类
实现Observer接口的唯一方法update
*/
function Observer(){
this.update = function(){ }
}
皮一下,这其实是看了Java里的观察者模式思想,我用Js实现了一下。因为我看这个实现思路是更能体会到观察者模式的。这里有两个类,一个是被观察类,实现了上述三个方法,一个是观察者类。每个观察者都有update方法,当被观察者改变了,执行相应的观察者的update方法来通知观察者被观察者改变了。下面看示例:
function thief(){
var data = 0;
Observable.call(this);
this.setData = function(param){
data = param;
if(data > 3){
this.setChange();
}
this.notifyObservers();
}
this.getData = function(){
return data
}
}
thief.prototype = new Observable();
thief.prototype.constructor = thief;
let thief1 = new thief();
let police1 = new Observer;
let police2 = new Observer;
thief1.addObserver(police1)
thief1.addObserver(police2)
police1.update = function(){
console.log('警察1接收到现在有'+thief1.getData()+'个小偷')
}
police2.update = function(){
console.log('警察2接收到现在有'+thief1.getData()+'个小偷')
}
thief1.setData(1) //无输出
thief1.setData(4) //警察1接收到现在有4个小偷 警察2接收到现在有4个小偷
这个例子小偷继承了被观察者,当小偷内部数据大于3的时候,就通知观察各个警察引起重视。因为是参照Java的思想来的,所以看到实现是通过将小偷继承被观察者类,警察继承观察者类。这个结构其实用java来写很清晰,观察者的模式也体现的很清晰,能帮助我们很好的理解观察者模式,值得借鉴。比起前端有些写法在被观察中存入对应的观察者,和观察者的回调,这种写法其实更分离。
三、前端代码写观察者模式
前端的写法跟Java里的实现类是有差别的,看代码:
/**
* 实现一个类
* on():存入被观察者
* fire():手动触发某个观察者,可带参数
* delete():删除指定观察者
* update():被观察者更新了,执行观察者回调通知观察者
* one():某个观察者只执行一次
* 可链式调用
*/
function Emiter(){
this.storage = new Map();
}
Emiter.prototype.on = function(key,callback){
this.storage.set(key,callback);
return this
}
Emiter.prototype.fire = function(key,data){
this.storage.has(key) ? this.storage.get(key)(data) : console.log('请先注册对应的事件');
return this
}
Emiter.prototype.delete = function(key){
this.storage.has(key) ? this.storage.delete(key) : console.log('请先注册对应的事件');
return this
}
Emiter.prototype.update = function(data){
for (let [key, value] of this.storage) {
value(data);
}
return this
}
Emiter.prototype.one = function(key,data){
if(this.storage.has(key)){
this.storage.get(key)(data);
this.storage.delete(key);
}else{
console.log('请先注册对应的事件')
}
return this
}
//示例
let emiter = new Emiter();
emiter.on('view1',function(data){
console.log('view1收到的数据'+data)
})
emiter.on('event',function(){
console.log('emiter')
}).on('click',function(){
console.log('click啦')
})
emiter.update([1,2,3]) //view1收到的数据1,2,3 emiter click啦
前端抛开一些复杂规范性,可以直接把观察者和观察者的回调保存在被观察者里,当被观察者更新了,执行所有观察者的回调。这里之所以有one fire 等方法提供,主要是考虑前端addEventListner也是采用观察者模式,所以一起提供了。只不过更相当于一对一模式,没有对应的update,更新所有观察者的操作。我们这里探讨的设计模式应该是on update delete为主线。
四、观察者模式的用途
观察者模式主要用于解耦合,一个对象变了,可以通知多个对象,而不是在回调里来处理各种情况。一个场景有个模块获取了数据,其他多个view层都需要用到这个数据。我们的做法可能是会在获取数据后的模块回调里进行各个view层的处理,那如果后续某个模块的相应业务变了,我们可能还得回来改这个回调函数。那如果通过观察者模式,我们可以优化为,每个模块完全分离,当数据回来时告知每个模块即可。以后模块要改变获取数据后的处理,各个模块自己去更新。
我现在在想,event对象,是不是内部实现,是在dom改变时传的参数,而在调用相关事件时能拿到这个回调的参数。
之前看文档,Vue里数据变了,如何更新view,用的也是观察者模式,只不过不是这种实现代码,后续更新。