观察者模式
观察者模式:定义了一种一对多的关系,让多个观察者对象同时监听某一个主体对象,这个主体对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
当我们不再希望某个特定的观察者被主体对象通知其变更时,该主体可以将其从观察者列表中删除。
所以观察者模式包括以下几个:
Subject: 主体对象(状态发布者),维护观察者列表,添加或删除观察者;
Observer: 观察者,知道自己观察的状态是描述的哪一个对象,提供更新操作
先来实现ObserverList
function ObserverList(){
this.observerList = [];
}
ObserverList.prototype.add = function( obj ){
return this.observerList.push( obj );
};
ObserverList.prototype.count = function(){
return this.observerList.length;
};
ObserverList.prototype.get = function( index ){
if( index > -1 && index < this.observerList.length ){
return this.observerList[ index ];
}
};
ObserverList.prototype.indexOf = function( obj, startIndex ){
var i = startIndex;
while( i < this.observerList.length ){
if( this.observerList[i] === obj ){
return i;
}
i++;
}
return -1;
};
ObserverList.prototype.removeAt = function( index ){
this.observerList.splice( index, 1 );
};
再来实现Subject
function Subject(){
this.observers = new ObserverList();
}
Subject.prototype.addObserver = function( observer ){
this.observers.add( observer );
};
Subject.prototype.removeObserver = function( observer ){
this.observers.removeAt( this.observers.indexOf( observer, 0 ) );
};
Subject.prototype.notify = function( context ){
var observerCount = this.observers.count();
for(var i=0; i < observerCount; i++){
this.observers.get(i).update( context );
}
};
最后我们来实现Observer
大概代码如下:
function Observer () {
this.update = function () {
// ...
}
}
我们来测试下:
var subject = new Subject();
var observer1 = new Observer();
observer1.update = function () {
console.log('observer1');
}
var observer2 = new Observer();
observer2.update = function () {
console.log('observer2');
}
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notify(); // 输出 observer1 observer2
发布订阅者模式
发布订阅者模式(Publish/Subscribe),事件的发布者只发布事件,不关心这个事件被谁获取了,通常将事件发给一个中间件,由中间件再去分发事件。
事件的订阅者只关心事件本身,不关心这个事件是谁发布的,通常在中间件中去注册观察某个事件。
中间件中去维护事件类别对应的订阅者列表,当收到事件后,去对应列表中通知订阅者。
创建一个观察者
观察者对象有一个消息容器,还包括三个方法,分别是订阅、发布订阅、取消订阅的消息方法。
var Observer = (function () {
var topics = {}; // 回调函数存放的数组
return {
// 订阅方法
subscribe: function () {},
// 发布方法
publish: function () {},
// 取消订阅方法
unsubscribe: function () {}
}
})();
订阅方法的作用是将订阅者注册的消息推入到消息队列中,因此我们需要接受两个参数:消息类型以及相应的处理动作。
subscribe: function (type, fn) {
// 如果此消息不存在则创建
if (!topics[type]) {
topics[type] = [];
}
// 将动作推入到该消息对应的动作执行序列中
topics[type].push(fn);
}
发布订阅方法的作用是当观察者发布一个消息时将所有订阅者订阅的消息一次执行。因此应接受两个参数:消息类型以及动作执行时需要传递的参数。
publish: function (type, args) {
// 如果该消息没有被注册,则返回
if (!topics[type]) {
return;
}
var self = this;
setTimeout(function () {
var subscribers = topics[type], len = subscribers ? subscribers.length: 0;
for (var i = 0; i < len; i++) {
// 依次执行注册的消息对应的动作序列
subscribers[i].call(self, args);
}
}, 0);
}
最后是取消订阅方法,其作用是将订阅者注销的消息从消息队列中清除,因此应接受两个参数:消息类型以及执行的某一动作。
unsubscribe: function (type, fn) {
var subscribers = topics[type];
// 判断是否存在
if (subscribers instanceof Array) {
var i = subscribers.length - 1;
for (;i >= 0; i-- ) {
// 如果存在该动作则在消息动作序列中移除响应动作
subscribers[i] === fn && subscribers.splice(i, 1);
}
}
}
我们来试试
var listener1 = function (data) {
console.log('listener1:' + data);
}
var listener2 = function (data) {
console.log('listener2:' + data);
}
Observer.subscribe('example1', listener1);
Observer.subscribe('example1', listener2);
//发布通知
Observer.publish('example1', 'hello world!');
Observer.publish('example1', ['test', 'a', 'b', 'c']);
输出结果:
测试下取消订阅:
Observer.unsubscribe('example1', listener2);
Observer.publish('example1', '123');
// 只输出 listener1:123
使用场景
- 是否观察的是状态(明确知道状态源),如果被观察和观察者双方需要明确知道对方,那就选择观察者模式、否则发布订阅者模式
- 一对一或者一对多的关系,这个事件或者状态只有一个发布者,两者都可以使用,再参考第一条;
- 多对多的关系,首先所谓对多对的关系基本就可以确定传递的是事件,而不是状态,因为不同对象不应该发布相同的状态,不要犹豫选发布/订阅
实际使用
Node.js中的EventEmitter、以及Vuejs中都使用到了该模式。
参考资料:
http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript