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']
});
posted @ 2015-10-19 23:34  JinksPeng  阅读(240)  评论(0编辑  收藏  举报