Javascript中介者模式

1 什么是中介者模式

JavaScript中介者模式是一种设计模式,它的目的是通过使用一个中介者对象来协调不同对象之间的交互,从而减少对象之间的直接通信。

这种模式通常用于大型系统中,其中有许多对象需要相互通信。中介者对象在对象之间充当一个中介,它接收来自一个对象的请求,并根据需要将其转发给其他对象。这种方式可以减少对象之间的耦合,使得系统更易于维护和扩展。

2 示例:泡泡堂游戏

假设有一个泡泡堂游戏,支持双人对战,当其中一个玩家死亡的时候,另一个玩家随之获胜,接下来用代码描述这个场景。

首先定义一个玩家构造函数,以及3个原型方法winlosedie

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();
};

创建两个玩家对象:

var player1 = new Player("皮蛋");
var player2 = new Player("小乖");

将玩家互相设置为敌人:

player1.enemy = player2;
player2.enemy = player1;

当玩家player1被泡泡炸死的时候,只需要调用这一句代码便完成了一局游戏:

player1.die(); // 输出:皮蛋 lost、小乖 won

2.1 增加游戏人数

现在游戏人数增加到8人,分成两队进行对抗,所以我们需要对上面的代码进行改进,在上面的代码中设置敌人非常低效,所以我们定义一个数组players来保存所有的玩家,在创建玩家之后,循环players来给每个玩家设置队友和敌人,再改写构造函数Player,使每个玩家对象都增加一些属性:

var players = [];

function Player(name, teamColor) {
  this.partners = []; // 队友列表
  this.enemies = []; // 敌人列表
  this.state = "live"; // 玩家状态
  this.name = name; // 角色名字
  this.teamColor = teamColor; // 队伍颜色
}

玩家死亡的方法要变得稍微复杂一点,我们需要在每个玩家死亡的时候,都遍历其他队友的生存状况,如果队友全部死亡,则这局游戏失败,同时敌人队伍的所有玩家都取得胜利,代码如下:

// 玩家死亡
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();
player4.die();
player3.die();

在这里插入图片描述

2.2 问题

现在我们已经可以随意地为游戏增加玩家或者队伍,但问题是,每个玩家和其他玩家都是紧紧耦合在一起的。

在此段代码中,每个玩家对象都有两个属性,this.partnersthis.enemies,用来保存其他玩家对象的引用。当每个对象的状态发生改变,比如角色移动、吃到道具或者死亡时,都必须要显式地遍历通知其他对象。

2.3 用中介者模式改造泡泡堂游戏

首先仍然是定义Player构造函数和player对象的原型方法,在player对象的这些原型方法中,不再负责具体的执行逻辑,而是把操作转交给中介者对象playerDirector

function Player(name, teamColor) {
  this.name = name; // 角色名字
  this.teamColor = teamColor; // 队伍颜色
  this.state = "alive"; // 玩家生存状态
}

// 玩家胜利
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.reciveMessage("playerDead", this); // 给中介者发送消息,玩家死亡
};

// 移除玩家
Player.prototype.remove = function () {
  playerDirector.reciveMessage("removePlayer", this); // 给中介者发送消息,移除一个玩家
};

// 玩家换队
Player.prototype.changeTeam = function (color) {
  playerDirector.reciveMessage("changeTeam", this, color); // 给中介者发送消息,玩家换队
};

var playerFactory = function (name, teamColor) {
  var newPlayer = new Player(name, teamColor); // 创造一个新的玩家对象
  playerDirector.reciveMessage("addPlayer", newPlayer); // 给中介者发送消息,新增玩家
  return newPlayer;
};

接下来实现这个中介者playerDirector对象,playerDirector开放一个对外暴露的接口reciveMessage,负责接收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 (var color in players) {
        if (color !== teamColor) {
          var teamPlayers = players[color]; // 其他队伍的玩家
          for (var i = 0, player; (player = teamPlayers[i++]); ) {
            player.win(); // 其他队伍所有玩家 win
          }
        }
      }
    }
  };
  var reciveMessage = function () {
    var message = Array.prototype.shift.call(arguments);
    operations[message].apply(this, arguments);
  };
  return {
    reciveMessage: reciveMessage,
  };
})();

可以看到,除了中介者本身,没有一个玩家知道其他任何玩家的存在,玩家与玩家之间的耦合关系已经完全解除,某个玩家的任何操作都不需要通知其他玩家,而只需要给中介者发送一个消息,中介者处理完消息之后会把处理结果反馈给其他的玩家对象。我们还可以继续给中介者扩展更多功能,以适应游戏需求的不断变化。

3 示例:购买商品

假设我们正在编写一个手机购买的页面,在购买流程中,可以选择手机的颜色以及输入购买数量,同时页面中有两个展示区域,分别向用户展示刚刚选择好的颜色和数量。还有一个按钮动态显示下一步的操作,我们需要查询该颜色手机对应的库存,如果库存数量少于这次的购买数量,按钮将被禁用并且显示库存不足,反之按钮可以点击并且显示放入购物车。

初始状态:

// 初始手机库存
var goods = {
  red: 3, // 红色3部
  blue: 6, // 蓝色6部
};

首先实现一个简单的购买页面:

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

在这里插入图片描述
接下来将分别监听colorSelectonchange事件函数和numberInputoninput事件函数,然后在这两个事件中作出相应处理。

var colorSelect = document.getElementById("colorSelect"),
  numberInput = document.getElementById("numberInput"),
  colorInfo = document.getElementById("colorInfo"),
  numberInfo = document.getElementById("numberInfo"),
  nextBtn = document.getElementById("nextBtn");

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 - 0) | 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, // 数量
    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 = "放入购物车";
};

3.1 问题

虽然目前顺利完成了代码编写,但随之而来的需求改变有可能给我们带来麻烦。

假设现在要求去掉colorInfonumberInfo这两个展示区域,我们就要分别改动colorSelect.onchangenumberInput.onput里面的代码,因为在先前的代码中,这些对象确实是耦合在一起的。

如何我们页面中将新增另外一个下拉选择框,代表选择手机内存。现在我们需要计算颜色、内存和购买数量,来判断nextBtn是显示库存不足还是放入购物车。这个需求会再次改动许多的代码,这是因为在目前的实现中,每个节点对象都是耦合在一起的,改变或者增加任何一个节点对象,都要通知到与其相关的对象。

3.2 引入中介者

现在我们来引入中介者对象,所有的节点对象只跟中介者通信,当发生了事件行为时,仅仅通知中介者它们被改变了,同时把自身当作参数传入中介者,以便中介者辨别是谁发生了改变。剩下的所有事情都交给中介者对象来完成,这样一来,无论是修改还是新增节点,都只需要改动中介者对象里的代码。

  选择颜色: <select id="colorSelect">
    <option value="">请选择</option>
    <option value="red">红色</option>
    <option value="blue">蓝色</option>
  </select>
  选择内存: <select id="memorySelect">
    <option value="">请选择</option>
    <option value="32G">32G</option>
    <option value="16G">16G</option>
  </select>
  输入购买数量: <input type="text" id="numberInput" /><br />
  您选择了颜色: <div id="colorInfo"></div><br />
  您选择了内存: <div id="memoryInfo"></div><br />
  您输入了数量: <div id="numberInfo"></div><br />
  <button id="nextBtn" disabled="true">请选择手机颜色和购买数量
// 手机库存
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");
  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 - 0) | 0) !== number - 0) {
        // 输入购买数量是否为正整数
        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);
};

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值