08中介者,装饰者

中介者模式

  • 解除对象与对象之间的耦合关系;增加一个中介者后,所有的对象都可以通过中介者通信而不是互相引用;

中介者模式例子

  • 游戏
function Player(name, teamColor) {
  this.state = 'live';
  this.name = name;
  this.teamColor = teamColor;
};

Player.prototype.win = function() {
  console.log(this.name + ' win');
};

Player.prototype.lose = function() {
  console.log(this.name + ' lose');
};

Player.prototype.die = function() {
  this.state = 'dead';
  playDirector.ReceiveMessage('playerDead', this);
};

Player.prototype.remove = function() {
  playDirector.ReceiveMessage('removePlayer', this);
};

Player.prototype.changTeam = function(color) {
  playDirector.ReceiveMessage('changTeam', this, color);
};
//因为工厂不需要设置关系,这个工厂函数几乎失去了意义
var playerFactory = function(name, teamColor) {
  var newPlayer = new Player(name, teamColor);
  playDirector.ReceiveMessage('addPlayer', newPlayer);
  return newPlayer;
};

var playDirector = (function() {
  var players = {},
      operations = {};
  
  operations.addPlayer = function(player) {
    var teamColor = player.teamColor;
    players[teamColor] = players[teamColor] || [];
    players[teamColor].push(player);
  };

  operations.removePlayer = function(player) {
    var teamColor = player.teamColor,
        teamPlayers = players[teamColor] || [];
    for(var i = teamPlayers.length - 1; i >= 0; i--) {//遍历删除
      if(teamPlayers[i] === player)
        teamPlayers.splice(i, 1);
    }
  };

  operations.changTeam = function(player, newTeamColor) {
    operations.removePlayer(player);
    player.teamColor = newTeamColor;
    operations.addPlayer(player);
  };

  operations.playerDead = function(player) {
    var teamColor = player.teamColor,
        teamPlayers = players[teamColor],
        all_dead = true;
    for(var i = 0, player; player = teamPlayers[i++];) {
      if(player.state != 'dead') {
        all_dead = false;
        break;
      }
    };
    if(all_dead === true) {
      for(var i = 0, player; player = teamPlayers[i++];)
        player.lose();
      for(var color in players) {
        if(color !== teamColor) {
          var teamPlayers = players[color];
          for(var i = 0, player; player = teamPlayers[i++];)
            player.win();
        }
      }
    }
  };

  var ReceiveMessage = function() {
    var message = [].shift.call(arguments);
    operations[message].apply(this, arguments);
  };
  return {
    ReceiveMessage: ReceiveMessage
  }

})();
//测试
var player1 = playerFactory('jinks', 'red');
var player2 = playerFactory('bob', 'red');

var player3 = playerFactory('jack', 'blue');
var player4 = playerFactory('tom', 'blue');

player3.changTeam();
player4.die();
  • 购买商品
var goods = {
  'red|32G': 3,
  'red|16G': 0,
  'blue|32G': 1,
  'blue|16G': 6
};

var mediator = (function() {
  var colorSelect = document.getElementById('colorSelect'),
      memorySelect = document.getElementById('memorySelect'),
      numberInput = document.getElementById('numberInput'),
      colorInfo = document.getElementById('colorInfo'),
      memoryInfo = document.getElementById('memoryInfo'),
      numberInfo = document.getElementById('numberInfo'),
      nextBtn = document.getElementById('nextBtn');

  var changed = function(obj) {
      var color = colorSelect.value,
          memory = memorySelect.value,
          number = numberInput.value,
          stock = goods[color + '|' + memory];
      switch(obj) {
        case colorSelect:
          colorInfo.innerHTML = color;
          break;
        case memorySelect:
          memoryInfo.innerHTML = memory;
          break;
        case numberInfo:
          numberInfo.innerHTML = number;
          break;
      }
      if(!color) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '请选择手机颜色';
        return;
      }
      if(!memory) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '请选择内存大小';
        return;
      }
      if(number > 0 && +number == number && !(number % 1) && number <= stock) {
        nextBtn.disabled = false;
        nextBtn.innerHTML = '放入购物车';
        return;
      }
      nextBtn.disabled = true;
      nextBtn.innerHTML = '请输入正确的购买数量';
    };
  return {
    changed: changed
  }
})();

colorSelect.onchange = function() {
   mediator.changed(this);
};
memorySelect.onchange = function() {
   mediator.changed(this);
};
numberInput.oninput = function() {
   mediator.changed(this);
}; 

与观察者模式相比

  • 都是通过消息传递实现对象和模块的接耦;
  • 观察者模式订阅是双向的.中介者模式订阅是单向的;
//中介者对象
var Mediator = function () {
  var _msg = {};
  return {
    register: function (type, action) {
      if(_msg[type])
      	_msg[type].push(action)
      else {
        _msg[type] = [];
        _msg[type].push(action)
      }
    },
    send: function (type) {
      if(_msg[type]) {
        for(var i = 0, l = _msg[type].length; i < l; i++) {
          _msg[type][i] && _msg[type][i]();
        }
      }
    }
  }
}();

Mediator.register('demo', function () {
  console.log('fist');
});

Mediator.register('demo', function () {
  console.log('second');
});

Mediator.send('demo');

小结

  • 中介者模式是最少知识原则的实现,即一个对象应该尽可能少地了解另外的对象;
  • 实际开发中,如果对象之间的耦合导致调用和维护困难且增加变量会使耦合几何增长,应该考虑中介者来重构;

装饰者模式

  • 可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象;
  • 传统面向对象语言中,给对象添加功能常常使用继承的方式;
    • 但这会导致超类和子类之间的强耦合;
    • 超类内部的细节是对子类可见的,继承常常被认为破坏了封装性;
    • 完成一些功能的复用的同时,有可能会创建出大量的子类;

装饰者也是包装器

  • 装饰者模式将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链;

模拟传统面向对象的装饰者模式

var A = function() {};
A.prototype.fire = function() {
  console.log('A fire');
};
var B = function(a) {
  this.a = a;
};
B.prototype.fire = function() {
  this.a.fire();
  console.log('B fire');
};
var C = function(a) {
  this.a = a;
};
C.prototype.fire = function() {
  this.a.fire();
  console.log('C fire');
};
//
var a = new A();
a = new B(a);
a = new C(a);

JS装饰者

var a = {
  fire: function() {
    console.log('a fire');
  }
};
var b = function() {
  console.log('b fire');
};
var c = function() {
  console.log('c fire');
};
var fire1 = a.fire;
a.fire = function() {
  fire1();
  b();
};
var fire2 = a.fire;
a.fire = function() {
  fire2();
  c();
}
a.fire();   
  • fire1,fire2为装饰函数;要注意装饰函数可能存在this劫持问题;

onClick例子

var decorator = function (query, fn) {
  var elem = document.querySelector(query);
  if (typeof elem.onclick === 'function') {
  	var oldClick = elem.onclick;
  	elem.onclick = function () {
  	  oldClick();
  	  fn();
  	}
  } else {
    elem.onclick = fn;
  }
  //....
}

用AOP装饰函数

Function.prototype.before = function(beforefn) {
  var _self = this;
  return function() {
    beforefn.apply(this, arguments)
    return _self.apply(this, arguments);
  }
};
Function.prototype.after = function(afterfn) {
  var _self = this;
  return function() {
    var ret = _self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  }
};
//
unction a() {
  console.log('a', arguments);
};
function b() {
  console.log('b', arguments);
};
a = a.after(b);
a(1,2,3);

AOP函数应用

  • 分离职责:如果一个函数内有多个层面的功能,可以使用AOP函数将其分离;
  • 动态改变函数参数:由于AOP中arguments对象引用都是相同的,可以使用before在要执行的函数执行之前修改参数
  • 插件式表单验证
Function.prototype.before = function(beforefn) {
  var _self = this;
  return function() {
    if(beforefn.apply(this, arguments) === false)
      return 
    return _self.apply(this, arguments);
  }
};
var validata = function() {
  if(usernam.value === '') {
    alert('用户名不得为空!');
    return false;
  }
  if(password.value === '') {
    alert('密码不得为空!');
    return false;
  }
};
var formSubmit = function() {
  var param = {
    usernam: usernam.value,
    password: password.value
  }
  ajax('http:....', param);
};
formSubmit = formSubmit.before(validata);
submitBtn.onclick = function() {
  formSubmit();
};
  • 要注意的是,通过装饰的函数是返回的新函数;在原函数上定义的属性会丢失;
原文地址:https://www.cnblogs.com/jinkspeng/p/4582461.html