介绍
观察者模式是软件设计模式的一种。在此种模式中,一个目标对象维持一系列依赖于它(观察者)的对象,将有关状态的任何更新自动通知给他们。
观察者设计模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
使用观察者模式的好处:
1、支持简单的广播通信,自动通知所有已经订阅过的对象。方便各种状态的管理。
2、页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
3、目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。
实现方式
观察者模式有很多实现方式,从根本上说,该模式必须包含两个角色:观察者和目标。目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。
具体实现代码:
if (!Array.prototype.forEach) { Array.prototype.forEach = function (fn, thisObj) { var scope = thisObj || window; for (var i = 0, j = this.length; i < j; ++i) { fn.call(scope, this[i], i, this); } }; } if (!Array.prototype.filter) { Array.prototype.filter = function (fn, thisObj) { var scope = thisObj || window; var a = []; for (var i = 0, j = this.length; i < j; ++i) { if (!fn.call(scope, this[i], i, this)) { continue; } a.push(this[i]); } return a; }; } //扩展函数扩展对象 function extend(obj, extension){ for(var key in obj){ extension[key] = obj[key] } } //观察者目标 function ObserverSubject(){ this.observerList = []; } ObserverSubject.prototype.addObserver = function(observer){ this.observerList.push(observer) }; ObserverSubject.prototype.removeObserver = function(observer){ this.observerList = this.observerList.filter(function(el) { return el !== observer }); } ObserverSubject.prototype.notify = function(context){ this.observerList.forEach(function(el, index) { el.update(context) }); } //观察者对象 function Observer() { this.update = function(content){ } } var observerSubjectIns = {}; extend(new ObserverSubject(),observerSubjectIns) var ob1 = new Observer(); ob1.update = function(content){ console.log('ob1 收到通知:' + content) } var ob2 = new Observer(); ob2.update = function(content){ console.log('ob2 收到通知:' + content) } observerSubjectIns.addObserver(ob1); observerSubjectIns.addObserver(ob2); observerSubjectIns.notify('吃饭'); observerSubjectIns.removeObserver(ob2); observerSubjectIns.notify('吃饭');
与发布(Publish)/订阅(Subscribe)模式的的区别
在很多地方,包括百度百科中,都把观察者(Observer)模式等同于发布(Publish)/订阅(Subscribe)模式
两者都在功能上都实现广播通知,但是两者还是存在着差异。我们先来看发布(Publish)/订阅(Subscribe)模式的定义
订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。
代码实现
var pubsub = {}; (function(q){ var topics = {}, subUid = -1; //发布广播事件 q.publish = function(topic,args){ if(!topics[topic]){ return false; } var subscribers = topics[topic], length = subscribers.length; while(length --){ subscribers[length].func(args); } } //通过特定的名称和回到函数订阅事件 q.subscribe = function(topic,func){ if(!topics[topic]){ topics[topic] = []; } var token = (++subUid).toString(); topics[topic].push({ token : token, func: func }) return token; } //通过特定的引用取消订阅 q.unsubscribe = function(token){ for(var m in topics){ var item = topics[m] if(item){ for(var i = 0 , length = item.length ; i < length ; ++ i){ if(item[i].token === token){ item.splice(i, 1); return token; } } } } return this; } })(pubsub) var sub1 = pubsub.subscribe('reader',function(content){ console.log('小明订阅购买' + content) }) var sub2 = pubsub.subscribe('reader',function(content){ console.log('小红订阅购买' + content) }) var sub3 = pubsub.subscribe('daily',function(content){ console.log('小红订阅购买' + content) }) pubsub.publish('reader','读者') pubsub.unsubscribe(sub1); pubsub.publish('reader','读者') pubsub.publish('daily','日报')
我们举个例子:
订阅刊物,我们有两种形式,一种方式是我们直接向刊物的发行单位订阅,这时候,我们需要知道这个发行单位,发行单位同时也必须知道读者,这样才能在在新一期的刊物发行的时候送到读者手里。
另外一种方式,我们直接向附近的报刊亭订阅。这种方式我们不需要关注发行单位,同时发行单位也不必知道订阅者是谁。当新一期刊物发行后,直接送到报刊亭,再由报刊亭统一的送达到读者手上。
第一种方式就是观察者模式,而第二种方式就是发布/订阅模式
总结
虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。