• 设计模式


    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 + 发布-订阅者模式 等等

  • 相关阅读:
    20200902
    20200808
    20200801
    20191017
    LeetCode #974. Subarray Sums Divisible by K 数组
    LeetCode #532. K-diff Pairs in an Array 数组 哈希 双指针
    LeetCode #234. Palindrome Linked List 链表 栈 链表逆置
    LeetCode #307. Range Sum Query
    LeetCode #45. Jump Game II 数组 贪心
    LeetCode #55. Jump Game 数组 贪心 线性DP 回溯
  • 原文地址:https://www.cnblogs.com/136asdxxl/p/9783657.html
Copyright © 2020-2023  润新知