zoukankan      html  css  js  c++  java
  • 05观察,命令

    观察者模式

    • 又叫发布订阅模式,定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知;
    • JS开发中,一般用事件模型替代传统的发布-订阅模式

    作用

    • 可以广泛应用于异步编程中,替代传统回调函数;如订阅ajax请求的error、succ事件;
    • 取代对象之间硬编码的通知机制,一个对象不再显示地调用另一个对象的接口;降低耦合度;

    通用实现

    var event = {
      cacheList: [],
      listen: function (key, fn) {
        if(!this.cacheList[key])
          this.cacheList[key] = [];
        this.cacheList[key].push(fn);
      },
      trigger: function() {
        var key = [].shift.call(arguments),
            fns = this.cacheList[key];
        if(!fns || !fns.length)
          return false;
        for(var i = 0, fn; fn = fns[i++];)
          fn.apply(this, arguments);
      },
      remove: function(key, fn) {
        var fns = this.cacheList[key];
        if(!fns)
          return false;
        if(!fn) { //如果没有传入具体的回调函数则取消所有的
          fns && (fns.length = 0);
        } else {
          for(var len = fns.length - 1; len >= 0; len--) { //反向遍历
            var _fn = fns[len];
            if(_fn === fn)
              fns.splice(len, 1);
          }
        } 
      }
    };
    //动态安装发布-订阅功能;
    var installEvent = function(obj) {
      for(prop in event) {
        if(event.hasOwnProperty(prop))
          obj[prop] = event[prop];
      }
    };
    //执行
    var publisher = {};
    installEvent(publisher);
    publisher.listen('click', function(data) {
      console.log('clicked! ' + data);
    });
    publisher.trigger('click', 'new data');
    publisher.remove('click');
    

    实际开发中的例子

    • 网站开发中多个模块的渲染必须在某个事件(模块)完成之后进行,仅利用回调可能会变成:
    login.succ(function(data) {
      A.start(data);
      B.start(data);
      .....
    });
    
    • 这个函数耦合度高,承担来太多功能,这时使用观察者模式:
    login.succ(function(data) {
      loginEvent.trigger('loginSucc', data);
    });
    
    var A = (function() {
      loginEvent.listen('loginSucc', function(data){
        A.start(data);
      });
      return {
        start: function(data) {
          consoole.log(data);
        }
      }
    })
    

    全局的发布-订阅对象

    • 可以使用一个全局Event对象,订阅者不用关心消息来自哪个发布者,发布者不管推送到哪里;
    • 去掉installEvent优化
    var Event = (function() {
      var cacheList = {},
          listen = function(){},
          triggee = function(){},
          remove = function(){};
      return {
        listen: listen,
        trigger: trigger,
        remove: remove
      }
    })
    

    可能的缺点

    • 消耗一定时间和内存,且当订阅一个消息但都未推送,这个订阅者始终存在于内存;
    • 使用太多全局发布-订阅对象,模块与模块之间的联系会被隐藏;会导致程序难以跟踪维护和理解;

    命令模式

    应用场景

    • 向某些对象发送请求,但并不知道请求的接受者是谁,也不知道被请求的操作是什么;
    • 将请求与实现解耦并封装成独立对象,从而使不同的请求对客户端实现参数化;
    • 传统命令模式实现
    /**
     * 按钮点击
     */
    var btn = document.getElementById('btn');
    var setCommand = function (btn, command) {
      btn.onclick = function() {
        command.execute();
      }
    };
    //实际设置的对象接受对象和请求操作
    var Menu = {
      num: 0,
      add: function() {
        this.num++;
        console.log(this.num);
      }
    };
    var MenuCommand = function(receive) {
      this.receive = receive;
    };
    MenuCommand.prototype.execute = function() {
      this.receive.refresh();
    };
    //使用
    var addMenu = new MenuCommand(Menu);
    setCommand(btn, addMenu)
    

    JS中的命令模式

    • JS中,由于函数是一等公民;与策略模式一样,已经融入了JS语言之中;
    • 其实是回调函数一个面向对象的替代品;
    //上面例子的简化
    var MenuCommand = function(receive) {
      return {
        execute: function() {
          receive.add();
        }
      }
    };
    

    撤销和重做

    • 在某些无法撤销操作的情况下,可以使用重做
     var btn = document.getElementById('btn');
     var btn2 = document.getElementById('btn2');
     var btn3 = document.getElementById('btn3');
     var setCommand = function (btn, command, fn) {
       btn.onclick = function() {
         Menu.commandStack.push(fn);
         command[fn]();
       }
     };
    
     var Menu = {
       num: 0,
       cache: [],
       commandStack: [],
       restartNum: 0,
       restartCache: [],
       add: function() {
         this.cache.push(this.num);
         this.num++;
         console.log(this.num);
       },
       back: function() {
         var cache = this.cache;
         if(cache.length)
           this.num = cache.pop();
         console.log(this.num);
       },
       restart: function() {
         var command;
         this.num = this.restartNum;
         this.cache = this.restartCache;
         this.commandStack.pop();
         while (command = this.commandStack.shift())
           MenuCommand(this)[command]();
         this.restartNum = this.num;
         this.restartCache = this.cache;
       }
     };
     var MenuCommand = function(receive) {
       return {
         execute: function() {
           receive.add();
         },
         undo: function() {
           receive.back();
         },
         restart: function() {
           receive.restart();
         }
       }
     };
     
     //使用
     var refreshMenu = MenuCommand(Menu);
     setCommand(btn, refreshMenu, 'execute');
     setCommand(btn2, refreshMenu, 'undo');
     setCommand(btn3, refreshMenu, 'restart');
    

    宏命令

    • 一组命令的集合,通过执行宏命令可以一次性操作一组命令
     var MacroCommand = function () {
       return {
         commandList: [],
         add: function(command) {
           this.commandList.push(command);
         },
         execute: function() {
           for(var i = 0, command; command = this.commandList[i++];)
            command.execute();
         }
       } 
     };
    

    一个标题图片视图的创建

    var viewCommand = (function () {
      var tpl = {
        product: [
          '<div>',
            '<img src="{#src#}"/>',
            '<p>{#text#}</p>',
          '</div>'
        ].join(''),
        title: [
          '<div class="title">',
            '<div class="main">',
            '<h2>{#title#}</h2>',
            '<p>{#tips#}</p>',
          '</div>'
        ].join('')
      };
      var html = '';
      function formateString (str, obj) {
        return str.replace(/{#(w+)#}/g, function (match, key) {
          return obj[key];
        })
      }
      var Action = {
        create: function (data, view) {
          if(data.length) {
            for(var i = 0, l = data.length; i < l; i++) {
              html += formateString(tpl[view], data[i]);
            }
          } else {
          	html += formateString(tpl[view], data);
          }
        },
        display: function (container, data, view) {
          if(data) {
            this.create(data, view);
          }
          document.getElementById(container).innerHTML = html;
          html = '';
        }
      };
      return function excute(msg) {
        msg.param = Object.prototype.toString.call(msg.param) === '[object Array]' ? msg.param : [msg.param];
        Action[msg.command].apply(Action, msg.param);
      }
    })();
    
    var titleData = {
      title: 'this is title',
      tips: 'this is tips'
    };
    var productData = [
      {
      	src: '..',
      	text: 'pig2'
      },{
      	src: '..',
      	text: 'pig3'
      }
    ];
    
    //创建标题模块
    viewCommand({
      command: 'display',
      param: ['title', titleData, 'title']
    });
    //创建一个图片
    viewCommand({
      command: 'create',
      param: [{
        src: '..',
        text: 'pig1'
      }, 'product']
    })
    //创建多个图片
    viewCommand({
      command: 'display',
      param: ['product', productData, 'product']
    });
    
  • 相关阅读:
    HDU 3081 Marriage Match II
    HDU 4292 Food
    HDU 4322 Candy
    HDU 4183 Pahom on Water
    POJ 1966 Cable TV Network
    HDU 3605 Escape
    HDU 3338 Kakuro Extension
    HDU 3572 Task Schedule
    HDU 3998 Sequence
    Burning Midnight Oil
  • 原文地址:https://www.cnblogs.com/jinkspeng/p/4582454.html
Copyright © 2011-2022 走看看