观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
主要解决的问题:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
下面通过一个小例子来理解观察者模式:
//观察者 var Observer = function(name){ this.name = name; this.updata = function(msg){ console.log(this.name + msg); } }; //被观察者 var Subject = { observers: [], //发布 publish: function(msg){ var arr = this.observers; for(var i = 0; i < arr.length; i++){ arr[i].updata(msg); } }, //添加订阅者 add: function(name){ var arr = this.observers; if(!arr.includes(name)){ arr.push(name); } }, //移除订阅者 remove: function(name){ var arr = this.observers, index = arr.indexOf(name); if(index === -1) return; arr.splice(index); } }; var joe = new Observer("joe"); var john = new Observer("john"); var jofun = new Observer("jofun"); Subject.add(joe); Subject.add(john); Subject.add(jofun); //被观察者发布消息 Subject.publish("收到订阅消息!");
这个例子是通过模拟传统语言java实现的观察者模式,实现的关键是在被观察者(Subject)内部用一个数组observers存放观察者。在JavaScript中,函数作为一等对象,并且可以作为参数将其传入到其他函数内部执行,所以JavaScript是通过回调函数实现的观察者模式,实现过程更简单、更便捷。
用JavaScript回调实现观察者模式:
//被观察者 var Subject = { observers: [], //发布 publish: function(){ var arr = this.observers; for(var i = 0; i < arr.length; i++){ arr[i](); } }, //添加订阅者 add: function(name){ var arr = this.observers; if(!arr.includes(name)){ arr.push(name); } }, //移除订阅者 remove: function(name){ var arr = this.observers, index = arr.indexOf(name); if(index === -1) return; arr.splice(index); } }; Subject.add(function(){ console.log("小明收到订阅消息!") }); //被观察者发布消息 Subject.publish();
上面的代码虽然实现了观察者模式,但是有明显的缺陷,存在匿名函数无法退订的问题。下面修改一下代码,实现退订功能:
//被观察者 var Subject = { //用对象存储观察者 observers: {}, //发布 publish: function(){ for(var k in this.observers){ this.observers[k](); } }, //添加订阅者 add: function(name, callback){ if(name in this.observers) return; this.observers[name] = callback; }, //移除订阅者 remove: function(name){ if(! name in this.observers) return; delete this.observers[name]; } }; Subject.add('foo',function(){ console.log("小明收到订阅消息!") }); Subject.add('bar',function(){ console.log("小红收到订阅消息!") }); //退订 Subject.remove('name') //被观察者发布消息 Subject.publish();
通用的观察者模式
为了方便使其他对象具有观察者发布订阅的功能,我们定义一个通用的函数,然后将该函数的功能应用到需要观察者功能的对象上,代码如下:
var Observer = { //订阅 add: function(name, callback){ if(name in this.observers) return; this.observers[name] = callback || null; }, //退订 remove: function(name){ if(! name in this.observers) return; delete this.observers[name]; }, //发布 publish: function(){ var observers = this.observers; for(var k in observers){ if(typeof observers[k] !== "function") continue; observers[k].apply(this.publish, arguments); } }, //使对象 obj具有观察者功能 create: function(obj){ var o = obj || {}; for(var k in this){ o[k] = this[k]; o.observers = {}; } return o; } };
接下来测试一下通用的观察者模式代码:
//创建观察者 var obser = Observer.create(); //订阅 obser.add('foo', function(msg){ console.log("小明收到" + msg); }); obser.add('bar', function(msg){ console.log("小红收到" + msg); }); //退订 obser.remove('foo'); //发布 obser.publish("来自天天时事周报的消息!"); //小红收到来自天天时事周报的消息!
观察者模式优缺点:
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。