Javascript设计模式之观察者模式的多个实现版本实例
介绍
观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
使用观察者模式的好处:
1.支持简单的广播通信,自动通知所有已经订阅过的对象。
2.页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
3.目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。
正文(版本一)
JS里对观察者模式的实现是通过回调来实现的,我们来先定义一个pubsub对象,其内部包含了3个方法:订阅、退订、发布。
varpubsub={}; (function(q){ vartopics={},//回调函数存放的数组 subUid=-1; //发布方法 q.publish=function(topic,args){ if(!topics[topic]){ returnfalse; } setTimeout(function(){ varsubscribers=topics[topic], len=subscribers?subscribers.length:0; while(len--){ subscribers[len].func(topic,args); } },0); returntrue; }; //订阅方法 q.subscribe=function(topic,func){ if(!topics[topic]){ topics[topic]=[]; } vartoken=(++subUid).toString(); topics[topic].push({ token:token, func:func }); returntoken; }; //退订方法 q.unsubscribe=function(token){ for(varmintopics){ if(topics[m]){ for(vari=0,j=topics[m].length;i<j;i++){ if(topics[m][i].token===token){ topics[m].splice(i,1); returntoken; } } } } returnfalse; }; }(pubsub));
使用方式如下:
//来,订阅一个 pubsub.subscribe('example1',function(topics,data){ console.log(topics+":"+data); }); //发布通知 pubsub.publish('example1','helloworld!'); pubsub.publish('example1',['test','a','b','c']); pubsub.publish('example1',[{'color':'blue'},{'text':'hello'}]);
怎么样?用起来是不是很爽?但是这种方式有个问题,就是没办法退订订阅,要退订的话必须指定退订的名称,所以我们再来一个版本:
//将订阅赋值给一个变量,以便退订 vartestSubscription=pubsub.subscribe('example1',function(topics,data){ console.log(topics+":"+data); }); //发布通知 pubsub.publish('example1','helloworld!'); pubsub.publish('example1',['test','a','b','c']); pubsub.publish('example1',[{'color':'blue'},{'text':'hello'}]); //退订 setTimeout(function(){ pubsub.unsubscribe(testSubscription); },0); //再发布一次,验证一下是否还能够输出信息 pubsub.publish('example1','helloagain!(thiswillfail)');
版本二
我们也可以利用原型的特性实现一个观察者模式,代码如下:
functionObserver(){ this.fns=[]; } Observer.prototype={ subscribe:function(fn){ this.fns.push(fn); }, unsubscribe:function(fn){ this.fns=this.fns.filter( function(el){ if(el!==fn){ returnel; } } ); }, update:function(o,thisObj){ varscope=thisObj||window; this.fns.forEach( function(el){ el.call(scope,o); } ); } }; //测试 varo=newObserver; varf1=function(data){ console.log('Robbin:'+data+',赶紧干活了!'); }; varf2=function(data){ console.log('Randall:'+data+',找他加点工资去!'); }; o.subscribe(f1); o.subscribe(f2); o.update("Tom回来了!") //退订f1 o.unsubscribe(f1); //再来验证 o.update("Tom回来了!");
如果提示找不到filter或者forEach函数,可能是因为你的浏览器还不够新,暂时不支持新标准的函数,你可以使用如下方式自己定义:
if(!Array.prototype.forEach){ Array.prototype.forEach=function(fn,thisObj){ varscope=thisObj||window; for(vari=0,j=this.length;i<j;++i){ fn.call(scope,this[i],i,this); } }; } if(!Array.prototype.filter){ Array.prototype.filter=function(fn,thisObj){ varscope=thisObj||window; vara=[]; for(vari=0,j=this.length;i<j;++i){ if(!fn.call(scope,this[i],i,this)){ continue; } a.push(this[i]); } returna; }; }
版本三
如果想让多个对象都具有观察者发布订阅的功能,我们可以定义一个通用的函数,然后将该函数的功能应用到需要观察者功能的对象上,代码如下:
//通用代码 varobserver={ //订阅 addSubscriber:function(callback){ this.subscribers[this.subscribers.length]=callback; }, //退订 removeSubscriber:function(callback){ for(vari=0;i<this.subscribers.length;i++){ if(this.subscribers[i]===callback){ delete(this.subscribers[i]); } } }, //发布 publish:function(what){ for(vari=0;i<this.subscribers.length;i++){ if(typeofthis.subscribers[i]==='function'){ this.subscribers[i](what); } } }, //将对象o具有观察者功能 make:function(o){ for(variinthis){ o[i]=this[i]; o.subscribers=[]; } } };
然后订阅2个对象blogger和user,使用observer.make方法将这2个对象具有观察者功能,代码如下:
varblogger={ recommend:function(id){ varmsg='dudu推荐了的帖子:'+id; this.publish(msg); } }; varuser={ vote:function(id){ varmsg='有人投票了!ID='+id; this.publish(msg); } }; observer.make(blogger); observer.make(user);
使用方法就比较简单了,订阅不同的回调函数,以便可以注册到不同的观察者对象里(也可以同时注册到多个观察者对象里):
vartom={ read:function(what){ console.log('Tom看到了如下信息:'+what) } }; varmm={ show:function(what){ console.log('mm看到了如下信息:'+what) } }; //订阅 blogger.addSubscriber(tom.read); blogger.addSubscriber(mm.show); blogger.recommend(123);//调用发布 //退订 blogger.removeSubscriber(mm.show); blogger.recommend(456);//调用发布 //另外一个对象的订阅 user.addSubscriber(mm.show); user.vote(789);//调用发布
jQuery版本
根据jQuery1.7版新增的on/off功能,我们也可以定义jQuery版的观察者:
(function($){ varo=$({}); $.subscribe=function(){ o.on.apply(o,arguments); }; $.unsubscribe=function(){ o.off.apply(o,arguments); }; $.publish=function(){ o.trigger.apply(o,arguments); }; }(jQuery));
调用方法比上面3个版本都简单:
//回调函数 functionhandle(e,a,b,c){ //`e`是事件对象,不需要关注 console.log(a+b+c); }; //订阅 $.subscribe("/some/topic",handle); //发布 $.publish("/some/topic",["a","b","c"]);//输出abc $.unsubscribe("/some/topic",handle);//退订 //订阅 $.subscribe("/some/topic",function(e,a,b,c){ console.log(a+b+c); }); $.publish("/some/topic",["a","b","c"]);//输出abc //退订(退订使用的是/some/topic名称,而不是回调函数哦,和版本一的例子不一样 $.unsubscribe("/some/topic");
可以看到,他的订阅和退订使用的是字符串名称,而不是回调函数名称,所以即便传入的是匿名函数,我们也是可以退订的。
总结
观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。