最近学习了观察者模式和发布/订阅模式,但是一直有种不得要领的感觉,今天重新复习了一遍又有了新的思考,记录一下学习收获。
观察者模式
概念引用原文的话如下:
The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state.
一个对象(subject)维护一个对象列表(observers),当发生任何状态改变时自动通知它们
根据概念画了一个大概的关系图
接下来直接看看代码的实现
先是一个observeList类,这个类有几个维护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类,用于给某个具体的被观察的对象继承,这个类有包装了新增观察者(addObserver)、移除观察者(removeObserver)以及通知观察者(notify)的方法。
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 );
}
};
复制代码
最后是一个观察者的类,这个其实并不是强制要求以这个类来实现,只是说明了观察者需要暴露一个update方法,当subject的实例发生变化时,subject实例使用notify方法去调用观察者的update方法。
function Observer(){
this.update = function(){
// ...
};
}
复制代码
刚开始看观察者模式的时候一直不是很明白这个设计在开发中的应用,今天又重新看的时候忽然想起来和之前看vue的双向绑定一部分的实现很像。在双向绑定里当我们改变一个对象的值的时候,其他使用了这个值的地方也会对应改变,这和观察者模式的设计是比较类似的。
发布/订阅模式
发布/订阅模式和观察者模式有点类似,但是发布/订阅模式不存在观察者与被观察者的关系,这种设计模式相对于观察者模式更加的松耦合,实现代码如下:
一个对象如果提供了支持Publish()、Subscribe()和unsubscribe()的功能实现,就可以使用Publish/Subscribe。
var pubsub = {};
(function(myObject) {
// Storage for topics that can be broadcast
// or listened to
var topics = {};
// A topic identifier
var subUid = -1;
// Publish or broadcast events of interest
// with a specific topic name and arguments
// such as the data to pass along
myObject.publish = function( topic, args ) {
if ( !topics[topic] ) {
return false;
}
var subscribers = topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func( topic, args );
}
return this;
};
// Subscribe to events of interest
// with a specific topic name and a
// callback function, to be executed
// when the topic/event is observed
myObject.subscribe = function( topic, func ) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = ( ++subUid ).toString();
topics[topic].push({
token: token,
func: func
});
return token;
};
// Unsubscribe from a specific
// topic, based on a tokenized reference
// to the subscription
myObject.unsubscribe = function( token ) {
for ( var m in topics ) {
if ( topics[m] ) {
for ( var i = 0, j = topics[m].length; i < j; i++ ) {
if ( topics[m][i].token === token ) {
topics[m].splice( i, 1 );
return token;
}
}
}
}
return this;
};
}( pubsub ));
复制代码
发布/订阅模式和vue的eventBus异曲同工,在实际应用中,因为任何地方我们都可以订阅(subscribe)一个事件,也可以在任何地方发布(public)一个事件,所以在这个设计模式中,谁发布或者谁订阅了事件是无法感知的,这也是和观察者模式最大的区别。
总结
刚开始学习观察者模式和发布/订阅模式真的是完全没有什么感觉,简单的说的就是代码都看懂了,但是不知道有什么用。今天重新看了一遍,仔细想了一下设计的用法,和实际开发中对应起来就特别有感觉了,学习设计模式对于我们阅读源码和开发中体会框架的设计理念非常有帮助。