設計模式 - 發布-訂閱者模式


1、發布-訂閱者 設計模式

定義

定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知

觀察者模式和發布訂閱模式區別

觀察者模式是由具體目標(發布者/被觀察者)調度的,而發布/訂閱模式是由獨立的調度中心進行調度,所以觀察者模式的訂閱者與發布者之間是存在依賴的,而發布/訂閱模式則不會;可以說發布訂閱模式是觀察者模式進一步解耦,在實際中被大量運用的一種模式

** 觀察者模式 **
1.定義/解析
目標和觀察者是基類,目標提供維護觀察者的一系列方法,觀察者提供更新接口。具體觀察者和具體目標繼承各自的基類,然后具體觀察者把自己注冊到具體目標里,在具體目標發生變化時候,調度觀察者的更新方法。

2.調度圖/流程圖

3.實現代碼

//觀察者列表
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(){
    // ...
  };
}

4.觀察者組成(java):

  • 抽象主題角色:把所有對觀察者對象的引用保存在一個集合中,每個抽象主題角色都可以有任意數量的觀察者。抽象主題提供一個接口,可以增加和刪* 除觀察者角色。一般用一個抽象類和接口來實現。
  • 抽象觀察者角色:為所有具體的觀察者定義一個接口,在得到主題的通知時更新自己。
  • 具體主題角色:在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色通常用一個子類實現。
  • 具體觀察者角色:該角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態相協調。通常用一個子類實現。如果需要,具體觀察者角色可以保存一個指向具體主題角色的引用。

5.類圖(java)

簡化版:

6.時序圖(java)

7.java實現代碼

/*抽象觀察者(Observer)*/
public interface Observer {
    public void update(String message);
}

/*具體觀察者(ConcrereObserver):實現抽象接口*/
public class WeixinUser implements Observer {
    // 微信用戶名
    private String name;
    public WeixinUser(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name + "-" + message);
    }
}

/*抽象被觀察者/目標對象/主題(Subject)*/
public interface Subject {
    /**
     * 增加訂閱者
     * @param observer
     */
    public void attach(Observer observer);
    /**
     * 刪除訂閱者
     * @param observer
     */
    public void detach(Observer observer);
    /**
     * 通知訂閱者更新消息
     */
    public void notify(String message);
}

/*具體被觀察者/目標對象/主題(Subject)*/
public class SubscriptionSubject implements Subject {
    //儲存訂閱公眾號的微信用戶
    private List<Observer> weixinUserlist = new ArrayList<Observer>();

    @Override
    public void attach(Observer observer) {
        weixinUserlist.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        weixinUserlist.remove(observer);
    }

    @Override
    public void notify(String message) {
        for (Observer observer : weixinUserlist) {
            observer.update(message);
        }
    }
}

/*客戶端調用*/
public class Client {
    public static void main(String[] args) {
        SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
        //創建微信用戶
        WeixinUser user1=new WeixinUser("楊影楓");
        WeixinUser user2=new WeixinUser("月眉兒");
        WeixinUser user3=new WeixinUser("紫軒");
        //訂閱公眾號
        mSubscriptionSubject.attach(user1);
        mSubscriptionSubject.attach(user2);
        mSubscriptionSubject.attach(user3);
        //公眾號更新發出消息給訂閱的微信用戶
        mSubscriptionSubject.notify("劉望舒的專欄更新了");
    }
}

** 發布/訂閱模式 **
1.定義/解析
訂閱者把自己想訂閱的事件注冊到調度中心,當該事件觸發時候,發布者發布該事件到調度中心(順帶上下文),由調度中心統一調度訂閱者注冊到調度中心的處理代碼

2.調度圖/流程圖

3.實現代碼

// 1.javascript經典版
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 ));


// 2.javascript通用版
var event = { 
        clientList: [], //訂閱者列表
        listen: function( key, fn ){ // 發布者監聽並訂閱事件 
                if ( !this.clientList[ key ] ){
                        this.clientList[ key ] = []; 
                }
                this.clientList[key].push(fn) // 訂閱的消息添加進緩存列表 
        },
        trigger: function() { // 觸發訂閱事件
                var key = Array.prototype.shift.call( arguments ), // (1);
                fns = this.clientList[ key ];
                if ( !fns || fns.length === 0 ){ // 如果沒有綁定對應的消息 
                        return false;
                }
                for( var i = 0, fn; fn = fns[ i++ ]; ){
                        fn.apply( this, arguments ); // (2) // arguments 是 trigger 時帶上的參數
                } 
        },
        remove: function( key, fn ){ // 取消訂閱
                var fns = this.clientList[ key ];
                if ( !fns ){ // 如果 key 對應的消息沒有被人訂閱,則直接返回 
                        return false;
                }
                if ( !fn ){ // 如果沒有傳入具體的回調函數,表示需要取消 key 對應消息的所有訂閱
                        fns && ( fns.length = 0 ); 
                }else{
                        for ( var l = fns.length - 1; l >=0; l-- ){ // 反向遍歷訂閱的回調函數列表 
                                var _fn = fns[ l ];
                                if ( _fn === fn ){
                                    fns.splice( l, 1 );
                                }
                        }
                }
        }
} 
 
// 這個函數可以給所有的對象都動態安裝發布—訂閱功能:
var installEvent = function( obj ){ 
        for ( var i in event ){
                obj[ i ] = event[ i ]; 
        }
};


4.實例:

var login = {}
installEvent(login) //  實例化發布-訂閱對象
 
$.ajax( 'http:// xxx.com?login', function(data){ // 登錄成功 
        login.trigger( 'loginSucc', data); // 發布登錄成功的消息 data
});
 
 
var header = (function(){ // header 模塊 
        login.listen( 'loginSucc', function( data){
        header.setAvatar( data.avatar );
        }); 
        return {
                setAvatar: function( data ){
                        // 具體操作代碼
                        console.log( '設置 header 模塊的頭像' );
                } 
        }
})();
 
var nav = (function(){ // nav 模塊
        login.listen( 'loginSucc', function( data ){
                監聽到登錄成功事件后回調操作(具體見return中)
                nav.setAvatar( data.avatar );
        }); 
        return {
                setAvatar: function( avatar ){ 
                        console.log( '設置 nav 模塊的頭像' );
                } 
        }
})();

5.加強版

********
 Event 對象 添加以下功能
 1、提供創建命名空間的功能
 2、可先發布,再訂閱
********
 
var Event = (function(){
     var global = this, 
                Event,
                _default = 'default';
 
    Event = function(){
                var _listen,
                _trigger,
                _remove,
                _slice = Array.prototype.slice, 
                _shift = Array.prototype.shift, 
                _unshift = Array.prototype.unshift, 
                namespaceCache = {},
                _create,
                find,
                each = function( ary, fn ){
                	var ret;
                	for ( var i = 0, l = ary.length; i < l; i++ ){
                		var n = ary[i];
                		ret = fn.call( n, i, n); 
                	}
                	return ret; 
                };
                _listen = function( key, fn, cache ){ 
                	if ( !cache[ key ] ){
                		cache[ key ] = []; 
                	}
                	cache[key].push( fn );
                };
                _remove = function( key, cache, fn){
                     if ( cache[ key ] ){
                         if( fn ){
                            for( var i = cache[ key ].length; i >= 0; i-- ){
                                if( cache[ key ][i] === fn) {
                                    cache[ key ].splice(i, 1)
                                } 
                            }
                         } else {
                            cache[ key ] = []
                         }
                     }
                };
                _trigger = function(){
                        var cache = _shift.call(arguments),
                                key = _shift.call(arguments), 
                                args = arguments,
                                _self = this,
                                ret,
                                stack = cache[ key ]; 
                        
                        if (!stack || !stack.length ){
                                return;
                        }
                        return each( stack, function(){
                            return this.apply( _self, args );
                        });
                };
                _create = function( namespace ){
                        var namespace = namespace || _default;
                        var cache = {},
                                offlineStack = [], 
                                ret = {
                                        listen: function( key, fn, last ){
                                                _listen(key, fn, cache);
                                                if ( offlineStack === null ){
                                                        return; 
                                                }
                                                if ( last === 'last' ){
                                                        offlineStack.length && offlineStack.pop()(); 
                                                }else{
                                                        each( offlineStack, function(){
                                                                this(); 
                                                        });
                                                }
 
                                                offlineStack = null; 
                                        },
                                        one: function( key, fn, last ){ 
                                                _remove( key, cache ); 
                                                this.listen( key, fn ,last );
                                        },
                                        remove: function( key, fn ){
                                                _remove( key, cache ,fn);
                                        },
                                        trigger: function(){
                                                var fn, args,
                                                _self = this;
                                                _unshift.call( arguments, cache ); 
                                                args = arguments;
                                                fn = function(){
                                                        return _trigger.apply( _self, args ); 
                                                };
                                                if ( offlineStack ){
                                                        return offlineStack.push( fn );
                                                }
                                                return fn(); 
                                        }
                                };
                        return namespace ? ( namespaceCache[ namespace ] ? namespaceCache[ namespace ] :namespaceCache[ namespace ] = ret ) : ret;
                };
 			 return {
                        create: _create,
                        one: function( key,fn, last ){ 
                                var event = this.create();
                                event.one( key,fn,last );
                        },
                        remove: function( key,fn ){
                                var event = this.create(); event.remove( key,fn );
                        },
                        listen: function( key, fn, last ){
                                var event = this.create(); 
                                event.listen( key, fn, last );
                        },
                        trigger: function(){
                                var event = this.create( );
                                event.trigger.apply( this, arguments ); 
                        }
                }; 
        }();
        return Event; 
})();

注:在java中,通常會把訂閱者對象自身當成引用傳入發布者對象中,同時訂閱者對象還需提供一個名為諸如 update 的方法,供發布者對象在適合的時候調用。而在 JavaScript 中,我們用注冊回調函數的形式來代替傳統的發布-訂閱模式

使用場景

前端 JS中的DOM事件的事件回調
前端框架 vue 雙向數據綁定的實現 defineProperty + 發布-訂閱者模式 等等


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM