要说起学习设计模式的动机实际上是为了准备面试,惭愧脸,但是也正是因为要面试所以促使我去学习,今天学习的就是观察者模式和发布订阅模式。
说起这两个模式确实很像,为了弄懂其中的不同点确实也花了不少功夫,主要参考了这篇博文观察者模式与发布/订阅模式区别,另外一篇就是曾探的《JavaScript设计模式与开发实践》一书中的《发布订阅模式》。两者结合大有所获。
观察者模式
首先来谈谈观察者模式,在观察者模式中,有两个角色一个是Subject,用来维护一个observer列表,另一个角色就是Observer(观察者),在Observer中定义了一个具体的update方法,用来执行相关操作。整个过程就是当某个值发生变化后,Subject调用notify方法(实际就是循环调用observerList中每个observer的update方法,并把新的值作为update的参数传递进去)。从中我们可以看出在Subject中直接调用了Observer中的方法,也就是说Subject和Observer的联系实际上是非常紧密的。
举个例子,现在有一个房东他要租房子,当有空房子的时候,他就会去通知曾经来询问的租户,那么这个时候房东就是直接知道租客的电话和需求(要住什么样的房子)的,也就是此时房东和租客之间实际上是存在联系的。
大致的流向图就像下面。
代码如下:
//观察者列表
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 );
};
//目标
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 );
}
};
//观察者
function Observer(){
this.update = function(){
// ...
};
}
发布订阅模式
前面说到Subject和Observer联系是非常紧密的,因为我们要在Subject中调用Observer中的方法。那么发布订阅模式就可以解耦合,把调用的任务交给一个调度中心(中介),让调度中心去通知各个订阅者。
接着上面的例子。房东有钱后,自己变懒了,他不想每次有房源后,自己还要亲自打电话通知之前预留电话想要租房的租客,因为自己还要记住那些的租客的电话和需求(有钱了不想干这些活,我要躺着赚钱)。于是他就找到了中介,每次空出房子后,直接告诉中介我这里有什么样的房子,中介这里记录着哪些租客有着什么样的需求,中介再去联系有这样需求的租客。那么这里房东和未来可能的租客之间是没有联系的,房东从此不用自己再去亲自打电话去通知每一个有着这样需求的租客,只需要告诉中介一个人就行,中介去通知。那么整个过程就如下面这样
var pubsub = {};
(function(myObject) {
// Storage for topics that can be broadcast
// or listened to
var topics = {};
// An 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 ));
这里为什么要用一个立即执行函数传递一个对象进去,因为我们可能有多个中介,每多一个中介,我们都会动态的去添加一些方法(即告诉中介如何去运作)。subUid就是用于方便取消订阅操作的,假如有一天你租到了自己满意的房子,你就要打电话告诉中介,不要再给我这个号码打电话了,我已经租到房子了。(不然天天都要被中介骚扰了)
总结:观察者模式和发布订阅模式的区别应该就是当有房源消息的时候,到底是谁来通知租客,观察者是房东自己本人,而发布订阅则是中介。
最后(欢迎大家关注我)
如果你觉得有所收获的话,欢迎点赞,欢迎到我的github上面star一下。