《JavaScript设计模式与开发实践》——第十四章(中介者模式)学习记录

中介者模式的作用就是解除对象与对象之间的紧耦合关系。所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。

中介者模式的例子–玩家对战游戏

玩家数目为2,所以当其中一个死亡游戏就结束,并且通知对手胜利:

function Player(name){
 this.name = name;
 this.enemy = null;//敌人
}
Player.prototype.win = function(){
 console.log(this.name + ' won');
}
Player.prototype.lose = function(){
 console.log(this.name + ' lost');
}
Player.prototype.die = function(){
 this.lose();
 this.enemy.win();
}
//  创建2个玩家对象
var player1 = new Player('小明');
var player2 = new Player('小强');
//  给玩家相互设置敌人
player1.enemy = player2;
player2.enemy = player1;
//player1失败
player1.die();//小明 lost 小强 won

为游戏增加队伍

// 现在改成8个玩家,定义一个数组players来保存所有的玩家,在创建玩家之后,循环players来给每个玩家设置队友和敌人:
var players = [];
// 再改写构造函数Player,为每个玩家对象增加一些属性。
function Player(name,teamColor){
  this.partners = [];//队友列表
  this.enemies = [];//敌人列表
  this.state = 'live';//玩家状态
  this.name = name;//角色名字
  this.teamColor = teamColor;//队伍颜色
}
Player.prototype.win = function(){
  console.log(this.name + ' won');
 }
 Player.prototype.lose = function(){
  console.log(this.name + ' lost');
 }
//  玩家死亡的时候,我们需要在每个玩家死亡的时候,都遍历其他玩家的生存状况,如果队友全部死亡,游戏失败,同时敌人队伍的玩家胜利
 Player.prototype.die = function(){
  var all_dead = true;
  this.state = 'dead';//设置玩家状态为死亡
  for(var i = 0,partner;partner = this.partners[i++];){
    if(partner.state!=='dead'){
      all_dead = false;
      break;
    }
  }
  if(all_dead === true){
    this.lose();
    for(var i = 0,partner;partner = this.partners[i++];){
      partner.lose();
    }
    for(var i = 0,enemy;enemy = this.enemies[i++];){
      enemy.win();
    }
  }
 }
 
//  定义一个工厂来创建玩家
var playerFactory = function(name,teamColor){
  var newPlayer = new Player(name,teamColor);//创建新玩家
  for(var i = 0,player;player = players[i++];){//通知所有的玩家,有新角色加入
    if(player.teamColor === newPlayer.teamColor){//如果是同一队的玩家
      player.partners.push(newPlayer);//相互添加到队友列表
      newPlayer.partners.push(player);
    }else{
      player.enemies.push(newPlayer);//相互添加到敌人列表
      newPlayer.enemies.push(player)
    }
  }
  players.push(newPlayer);
  return newPlayer;
};

// 创建8个玩家
// 红队
var player1 = playerFactory('小明','red'),
    player2 = playerFactory('小红','red'),
    player3 = playerFactory('小丽','red'),
    player4 = playerFactory('小花','red');
// 蓝队
var player5 = playerFactory('小赵','blue'),
    player6 = playerFactory('小李','blue'),
    player7 = playerFactory('小孙','blue'),
    player8 = playerFactory('小周','blue');
// 让红队玩家全部死亡:
player1.die();
player2.die();
player3.die();
player4.die();

现在我们已经可以随意地为游戏增加玩家或者队伍,但是每个玩家和其他玩家都是紧紧耦合在一起的。如果是一个大型的网络游戏,如果一个玩家掉线,必须从所有其他玩家的队友列表和敌人列表中都移除这个玩家,显然我们上面的代码也要迅速进入投降模式了。

用中介者模式改造对战游戏

// 在player对象的这些原型方法中,不再负责具体的执行逻辑,而是把操作转交给中介者playerDirector
function Player(name,teamColor){
  this.state = 'live';//玩家状态
  this.name = name;//角色名字
  this.teamColor = teamColor;//队伍颜色
}
Player.prototype.win = function(){
  console.log(this.name + ' won');
 }
 Player.prototype.lose = function(){
  console.log(this.name + ' lost');
 }
//  玩家死亡
Player.prototype.die = function(){
  this.state = 'dead';
  playerDirector.ReceiveMessage('playerDead',this);//给中介者发送消息,玩家死亡
}
// 移除玩家
Player.prototype.remove = function(){
  playerDirector.ReceiveMessage('removePlayer',this);//给中介者发送消息,移除一个玩家
}
// 玩家换队
Player.prototype.changeTeam = function(color){
  playerDirector.ReceiveMessage('changeTeam',this,color);//玩家换队
}
// 改写之前创建玩家对象的工厂函数
var playerFactory = function(name,teamColor){
  var newPlayer = new Player(name,teamColor);//创建新玩家
  playerDirector.ReceiveMessage('addPlayer',newPlayer);//给中介者发送消息,新增玩家 
  return newPlayer;
};
// playerDirector开放一个对外暴露的接口ReceiveMessage,负责接收player对象发送的消息,而player对象发送消息的时候,总把自身this作为参数发送给playerDirector,以便playerDirector识别消息来自于哪个玩家对象
var playerDirector = (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.changeTeam = function(player,newTeamColor){
    operations.removePlayer(player);//从原队伍中删除
    player.teamColor = newTeamColor;//改变队伍颜色
    operations.addPlayer(player);//增加到新队伍中
  };
  // 玩家死亡
  operations.playerDead = function(player){
    var teamColor = player.teamColor,
        teamPlayers = players[teamColor];//该队伍所有成员

    var 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();//本队所有玩家lose
      }
      for(color in players){
        if(color!=teamColor){
          var teamPlayers = players[color];//其他队伍的玩家
          for(var i = 0,player;player = teamPlayers[i++];){
            player.win();//其他队伍的所有玩家win
          }
        }
      }
    }
  };

  var ReceiveMessage = function(){
    var message = Array.prototype.shift.call(arguments);//arguments的第一个参数为消息名称
    operations[message].apply(this,arguments);
  };
  return {
    ReceiveMessage:ReceiveMessage
  }
})();
 // 创建8个玩家
// 红队
var player1 = playerFactory('小明','red'),
    player2 = playerFactory('小红','red'),
    player3 = playerFactory('小丽','red'),
    player4 = playerFactory('小花','red');
// 蓝队
var player5 = playerFactory('小赵','blue'),
    player6 = playerFactory('小李','blue'),
    player7 = playerFactory('小孙','blue'),
    player8 = playerFactory('小周','blue');

 // 让红队玩家全部死亡:
  player1.die();
  player2.die();
  player3.die();
  player4.die();

在这里插入图片描述
假设小明和小红掉线,小丽和小花死亡

  player1.remove();
  player2.remove();
  player3.die();
  player4.die();

在这里插入图片描述
假设小明去了蓝队

player1.changeTeam();
player2.die();
player3.die();
player4.die();

在这里插入图片描述

中介者模式的例子–购买商品

现在有一个手机购买的页面,在购买流程中,可以选择手机的颜色和输入购买数量,同时页面中有两个展示区域,分别展示选择的颜色和数量,还有一个按钮显示下一步的操作,选择颜色以后,输入数量,判断是够还有库存,控制按钮是否禁用以及按钮的显示文字:
确定代码编写的5个节点:

  1. 下拉选择框colorSelect
  2. 文本输入框numberInput
  3. 展示颜色信息 colorInfo
  4. 展示购买数量信息numberInfo
  5. 决定下一步操作的按钮nextBtn
 <div> 选择颜色:<select id="colorSelect">
    <option value="">请选择</option>
    <option value="red">红色</option>
    <option value="blue">蓝色</option>
  </select></div>
 
  <div>输入购买数量:<input type="text" id="numberInput"/></div>
  <div>您选择了颜色:<span id="colorInfo"></span></div>
  <div>您输入了数量:<span id="numberInfo"></span></div>
  <br/>
  <button id="nextBtn" disabled>请选择手机颜色和购买数量</button>

监听colorSelect的onchange事件函数和numberInput的oninput事件函数,然后在这两个事件中作出相应处理:

var colorSelect = document.getElementById('colorSelect'),
    numberInput = document.getElementById('numberInput'),
    colorInfo = document.getElementById('colorInfo'),
    numberInfo = document.getElementById('numberInfo'),
    nextBtn = document.getElementById('nextBtn');
var goods = {//手机库存
  "red":3,
  "blue":6
}
colorSelect.onchange = function(){
  var color = this.value,//颜色
  number = numberInput.value,//数量
  stock = goods[color];//该颜色手机对应的当前库存

  colorInfo.innerHTML = color;
  if(!color){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择手机颜色';
    return;
  }
  if(Number.isInteger(number - 0)&& number > 0){//用户输入的购买数量是否为正整数
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请输入正确的购买数量';
    return;
  }
  if(number > stock){//当前选择数量超过库存量
    nextBtn.disabled = true;
    nextBtn.innerHTML = '库存不足';
    return;
  }
  nextBtn.disabled = false;
  nextBtn.innerHTML = '放入购物车';
}

触发了colorSelect的onchange事件以后,我们要让colorinfo中显示当前选中的颜色,然后获取用户当前输入的购买数量,对用户的输入值进行判断,再根据库存数量来判断nextBtn的显示状态:

numberInput.oninput = function(){
  var color = colorSelect.value,//颜色
      number = this.value,//数量
      stock = goods[color];//该颜色手机对应的当前库存
  numberInfo.innerHTML = number;
  if(!color){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择手机颜色';
    return;
  }
  if(((number - 0)|0)!==number - 0){//输入购买数量是否为正整数
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请输入正确的购买数量';
    return;
  }
  if(number > stock){//当前选择数量超过库存量
    nextBtn.disabled = true;
    nextBtn.innerHTML = '库存不足';
    return;
  }
  nextBtn.disabled = false;
  nextBtn.innerHTML = '放入购物车';
}

虽然目前顺利完成了代码编写,但是假设现在要去掉颜色和数量这两个展示区域,我们就要改变两处代码,这些对象是耦合在一起的。如果这个页面里的节点增加了10个或者15个,它们之间的联系可能变得更加错综复杂,每一次修改我们就会很难受,为了证明我们新增一个选择手机内存的下拉选择框:
增加两个HTML节点:

<div> 选择颜色:<select id="colorSelect">
  <option value="">请选择</option>
  <option value="red">红色</option>
  <option value="blue">蓝色</option>
</select></div>
<div> 选择内存:<select id="memorySelect">
  <option value="">请选择</option>
  <option value="32G">32G</option>
  <option value="16G">16G</option>
</select></div>
<div>输入购买数量:<input type="text" id="numberInput"/></div>
<div>您选择了颜色:<span id="colorInfo"></span></div>
<div>您选择了内存:<span id="memoryInfo"></span></div>
<div>您输入了数量:<span id="numberInfo"></span></div>
<br/>
<button id="nextBtn" disabled>请选择手机颜色和购买数量</button>

修改库存的json对象以及修改colorSelect的change事件函数:

var colorSelect = document.getElementById('colorSelect'),
   memorySelect = document.getElementById('memorySelect'),
   numberInput = document.getElementById('numberInput'),
   colorInfo = document.getElementById('colorInfo'),
   numberInfo = document.getElementById('numberInfo'),
   memoryInfo = document.getElementById('memoryInfo'),
   nextBtn = document.getElementById('nextBtn');
// 修改库存的json对象以及修改colorSelect的change事件函数:
var goods = {//手机库存
  "red|32G":3,
  "red|16G":0,
  "blue|32G":1,
  "blue|16G":1
}
colorSelect.onchange = function(){
  var color = this.value,//颜色
  memory = memorySelect.value,
  stock = goods[color+'|'+memory];//该颜色手机对应的当前库存

  number = numberInput.value,//数量
  colorInfo.innerHTML = color;
  numberInfo.innerHTML = number;
  if(!color){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择手机颜色';
    return;
  }
  if(!memory){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择内存大小';
    return;
  }
  if(!Number.isInteger(number - 0) ||  number <= 0){//输入购买数量是否为正整数
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请输入正确的购买数量';
    return;
  }
  if(number > stock){//当前选择数量超过库存量
    nextBtn.disabled = true;
    nextBtn.innerHTML = '库存不足';
    return;
  }
  nextBtn.disabled = false;
  nextBtn.innerHTML = '放入购物车';
}
numberInput.oninput = function(){
  var color = colorSelect.value,//颜色
      number = this.value,//数量
      memory = memorySelect.value,
      stock = goods[color+'|'+memory];//该颜色手机对应的当前库存
      memoryInfo.innerHTML = memory;
      numberInfo.innerHTML = number;
  if(!color){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择手机颜色';
    return;
  }
  if(!memory){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择内存大小';
    return;
  }
  if(!Number.isInteger(number - 0) || number <= 0){//输入购买数量是否为正整数
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请输入正确的购买数量';
    return;
  }
  if(number > stock){//当前选择数量超过库存量
    nextBtn.disabled = true;
    nextBtn.innerHTML = '库存不足';
    return;
  }
  nextBtn.disabled = false;
  nextBtn.innerHTML = '放入购物车';
}
memorySelect.onchange = function(){
  var color = colorSelect.value,//颜色
  number = numberInput.value,//数量
  memory = this.value,
  stock = goods[color+'|'+memory];//该颜色手机对应的当前库存
  memoryInfo.innerHTML = memory;
  numberInfo.innerHTML = number;
  if(!color){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择手机颜色';
    return;
  }
  if(!memory){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择内存大小';
    return;
  }
  if(!Number.isInteger(number - 0) ||  number <= 0){//输入购买数量是否为正整数
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请输入正确的购买数量';
    return;
  }
  if(number > stock){//当前选择数量超过库存量
    nextBtn.disabled = true;
    nextBtn.innerHTML = '库存不足';
    return;
  }
  nextBtn.disabled = false;
  nextBtn.innerHTML = '放入购物车';
}

加一个节点要修改很多地方,每个节点都耦合在一起,接下来引入中介者:

var goods = {//手机库存
 "red|32G":3,
 "red|16G":0,
 "blue|32G":1,
 "blue|16G":1
}
var mediator = (function(){
 var colorSelect = document.getElementById('colorSelect'),
     memorySelect = document.getElementById('memorySelect'),
     numberInput = document.getElementById('numberInput'),
     colorInfo = document.getElementById('colorInfo'),
     numberInfo = document.getElementById('numberInfo'),
     memoryInfo = document.getElementById('memoryInfo'),
     nextBtn = document.getElementById('nextBtn');
 return {
   changed:function(obj){
     var color = colorSelect.value,//颜色
         memory = memorySelect.value,//内存
         number = numberInput.value,//数量
         stock = goods[color+'|'+memory];//该颜色手机对应的当前库存
     if(obj === colorSelect){//如果改变的是选择颜色下拉框
       colorInfo.innerHTML = color;
     }else if(obj === memorySelect){
       memoryInfo.innerHTML = memory;
     }else if(obj === numberInput){
       numberInfo.innerHTML = number;
     }
     if(!color){
       nextBtn.disabled = true;
       nextBtn.innerHTML = '请选择手机颜色';
       return;
     }
     if(!memory){
       nextBtn.disabled = true;
       nextBtn.innerHTML = '请选择内存大小';
       return;
     }
     if(!Number.isInteger(number - 0) ||  number <= 0){//输入购买数量是否为正整数
       nextBtn.disabled = true;
       nextBtn.innerHTML = '请输入正确的购买数量';
       return;
     }
     if(number > stock){//当前选择数量超过库存量
       nextBtn.disabled = true;
       nextBtn.innerHTML = '库存不足';
       return;
     }
     nextBtn.disabled = false;
     nextBtn.innerHTML = '放入购物车';
   }
 }
})();
// 事件函数:
colorSelect.onchange = function(){
 mediator.changed(this);
}
memorySelect.onchange = function(){
 mediator.changed(this);
}
numberInput.oninput = function(){
 mediator.changed(this);
}

这时我们新增一些节点,比如CPU型号,那我们只需要稍稍改动下mediator对象即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值