zoukankan      html  css  js  c++  java
  • 设计模式

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

  • 相关阅读:
    POJ 1469 COURSES 二分图最大匹配
    POJ 1325 Machine Schedule 二分图最大匹配
    USACO Humble Numbers DP?
    SGU 194 Reactor Cooling 带容量上下限制的网络流
    POJ 3084 Panic Room 求最小割
    ZOJ 2587 Unique Attack 判断最小割是否唯一
    Poj 1815 Friendship 枚举+求最小割
    POJ 3308 Paratroopers 最小点权覆盖 求最小割
    1227. Rally Championship
    Etaoin Shrdlu
  • 原文地址:https://www.cnblogs.com/136asdxxl/p/9783657.html
Copyright © 2011-2022 走看看