观察者模式(Observer Pattern),也被称为“发布/订阅模型(publisher/subscriber model)”。在这种模式中,有两类对象,分别是“观察者-Observer”和“目标对象-Subject”。目标对象中保存着一份观察者的列表,当目标对象的状态发生改变的时候就主动向观察者发出通知(调用观察者提供的方法),从而建立一种发布/订阅的关系。这一种发布/订阅的关系常用于实现事件、消息的处理系统。
在我们的生活中,也存在着许多观察者模式,最简单的例子就是“微博”。关注和被关注的关系,其实就是一个发布/订阅模型。假如,方舟子“悄悄关注”了天才韩寒,韩寒在微博上每发出一条消息都会反馈到方舟子的消息列表中,方舟子便可端坐家中,阴阴一笑,“嘿嘿,小子你干了什么我都知道……”,然后方舟子就开始行动了。
传统的观察者模式
我们先看看传统的观察者模式是怎样的吧(Java版):
//被观察者
public class Subject{
private Array obArray = new Array();
//增加一个观察者
public void addObserver(Observer o){
this.obArray.add(o);
}
//删除一个观察者
public void removeObserver(Observer o){
this.obArray.remove(o);
}
//通知所有观察者
public void notifyObservers(){
for(Observer o: this.obArray){
o.update();
}
}
public void doSomething(){
//更新状态,告诉所有观察者
this.notifyObservers();
}
}
//观察者
public class Observer{
public void update(){
//目标对象更新了,要做点什么了
}
}
使用观察者模式的好处是,当一个对象A需要通知另外一个对象B的时候,无需在A中声明B,在保持相关对象行为一贯性的时候,避免对象之间的紧耦合,这样可以使对象有很好的重用性。
DOM中的观察者模式
JavaScript是一个事件驱动型语言,观察者模式可谓随处可见,例如:
document.body.onclick = function(){
alert('我是一个观察者,你一点击,我就知道了');
}
//或者是
document.body.addEventListener('click',function(){
alert('我也是一个观察者,你一点击,我就知道了');
});
这个例子中的发布/订阅关系是由JavaScript语言本身实现的,DOM的每个节点都可以作为Subject,提供了很多事件处理(Event handle)的接口,你只需要给这些接口添加监听函数(也就是Observer),就可以捕获触发的事件进行处理。
JavaScript的观察者模式
然而在我们自己写的对象中,要实现这种发布/订阅的关系,就需要自己来实现这个观察者模型,例如:
var ObserverPattern= function(){
//基于事件的观察者列表
this.eventObsArray = {};
}
ObserverPattern.prototype = {
//通知某个事件的所有观察者
notifyObservers: function(eventName,datas){
var observers= this.eventObsArray[eventName]||[],i,ob;
for(i=0;ob=observers[i];i++){
ob.handler.apply(ob.scope,datas||[]);
}
},
//给某个事件添加观察者
addObserver: function(eventName,handleFunction,observer){
var events = this.eventObsArray,
events[eventName] = events[eventName]||[];
events[eventName].push({
//传入的observer参数是handleFunction中的this
scope: observer || this,
handler: handleFunction
});
},
//取消某个观察者对某事件的观察
removeObserver: function(eventName,observer){
var evts = this.eventObsArray;
if(!evts[eventName]) return;
evts[eventName]=evts[eventName].filter(function(ob){
return ob.scope!=observer;
});
}
}
var 韩寒 = new ObserverPattern();
var 方舟子 = {
doSomeResearch: function(){alert('嘿嘿…我在搞研究…')}
}
//韩寒一写博客,方舟子就开始研究了
韩寒.addObserver('写博客',function(){
this.doSomeResearch();
},方舟子);
然而这种形式的发布/订阅模型,还是有些不足的地方,整个关系链条是由目标对象维护的,观察者无法主动去监听目标对象的变化;其次,观察者不知道其他观察者的存在,有时一个观察者的处理有时还会触发其他的事件,无法让其他观察者进行后续处理。
既是目标对象也是观察者
方舟子观察韩寒,难道韩寒就不可以看看方舟子了?其实,目标对象也可以是观察者,咱们对上面的ObserverPattern再改进改进:
var ObserverPattern= function(obj){
for(var i in obj){
this[i] = obj[i];
}
this.eventObsArray = {};
}
ObserverPattern.prototype = {
//监听某个目标对象
listen: function(subject, eName, handler){
subject.addObserver(eName, handler, this)
},
//取消监听某个目标对象
ignore: function(subject, eName){
subject.removeObserver(eName,this);
},
//之前定义的方法,这里就不多说了
notifyObservers: function(eName,datas){},
addObserver: function(eName,handler,ob){},
removeObserver: function(eName,ob){}
}
var 韩寒 = new ObserverPattern({
postReward: function(){alert('研究吧, 奖金2000万…')},
writeBlog: function(){this.notifyObservers('写博客')}
});
var 方舟子 = new ObserverPattern({
doSomeResearch: function(){
alert('嘿嘿…我在搞研究…');
this.notifyObservers('搞研究')
}
});
//韩寒一发微博,方舟子就开始研究了
方舟子.listen(韩寒,'写博客',方舟子.doSomeResearch);
//方舟子一开始研究,韩寒就发赏金了
韩寒.listen(方舟子,'搞研究',韩寒.postReward);
同时,当把目标对象和观察者整合到一起的时候,就形成了一条事件的触发链,一个事件可以触发另一个事件,一个观察者可以将自己观察的结果告诉其他观察者。当然,也要小心事件的循环促发,或者像”蝴蝶效应”那样让一个无关紧要的事件产生过大的影响。
更加灵活的事件管理方式
上面的ObserverPattern已经相对完善了,但是使用起来还是有不少限制。例如,需要保证目标对象和观察者先被创建才被调用;一个事件只能被一个目标对象触发,无法一个事件监听多个消息来源。虽然这些也不算什么大问题,但是还有一种更加灵活的方式来管理我们的事件。
//全局的事件监听模块,可用于对象之间的消息传递
var Event = (function(){
var events = {},
registerEvent = function(eName, handler, scope){
events[eName] = events[eName] || [];
events[eName].push({
scope: scope || this,
handler: handler
});
},
removeEvent = function(eName, handler, scope){
scope = scope || this;
if(!fns) return;
events[eName] = events[eName].filter(function(fn){
return fn.scope!=scope || fn.handler!=handler
});
},
triggerEvent = function(eventName,params){
var fns = events[eventName],i,fn;
if(!fns) return;
for(i=0;fn=fns[i];i++){
fn.handler.apply(fns.scope,params||[]);
}
};
return {
listen: registerEvent,
ignore: removeEvent,
trigger: triggerEvent
}
})();
Event.listen('韩寒写博客', 方舟子.doSomeResearch, 方舟子);
(function(){
alert('我是路人甲,我告诉方舟子,韩寒写博客了');
Event.trigger('韩寒写博客');
})();
这种方式的确更为灵活,但越是灵活就越是不好把握,这是一把双刃剑,要小心使用。这种情况下,观察者与目标对象之间的依存关系是很难被跟踪的,很容易像“蝴蝶效应”那样产生意想不到的结果。
最后说一下,韩粉方粉别太在意,我不是故意拿你们教主来开刷的,只是碰巧这样很形象嘛 ~
相关文章