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']
    });
    
  • 相关阅读:
    爱情七十八课,闲了就“犯贱”
    阿里巴巴中文站的CSS设计规则(转)
    爱情八十一课,可预测的分手
    [性格][管理]《九型人格2》 唐·理查德·里索(美)、拉斯·赫德森(美)
    爱情八十二课,爱情三国杀
    爱情七十九课,不爱权力大
    [心理学]《爱情心灵安全岛》 四四
    一些你不知道的囧知识,保证让你崩溃
    爱情七十四课,我们的意义
    爱情七十六课,门当户对
  • 原文地址:https://www.cnblogs.com/jinkspeng/p/4582454.html
Copyright © 2011-2022 走看看