JavaScript
中介者模式是一种设计模式,它的目的是通过使用一个中介者对象来协调不同对象之间的交互,从而减少对象之间的直接通信。
这种模式通常用于大型系统中,其中有许多对象需要相互通信。中介者对象在对象之间充当一个中介,它接收来自一个对象的请求,并根据需要将其转发给其他对象。这种方式可以减少对象之间的耦合,使得系统更易于维护和扩展。
假设有一个泡泡堂游戏,支持双人对战,当其中一个玩家死亡的时候,另一个玩家随之获胜,接下来用代码描述这个场景。
首先定义一个玩家构造函数,以及3个原型方法win
、lose
、die
:
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
现在游戏人数增加到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();
现在我们已经可以随意地为游戏增加玩家或者队伍,但问题是,每个玩家和其他玩家都是紧紧耦合在一起的。
在此段代码中,每个玩家对象都有两个属性,this.partners
和this.enemies
,用来保存其他玩家对象的引用。当每个对象的状态发生改变,比如角色移动、吃到道具或者死亡时,都必须要显式地遍历通知其他对象。
首先仍然是定义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,
};
})();
可以看到,除了中介者本身,没有一个玩家知道其他任何玩家的存在,玩家与玩家之间的耦合关系已经完全解除,某个玩家的任何操作都不需要通知其他玩家,而只需要给中介者发送一个消息,中介者处理完消息之后会把处理结果反馈给其他的玩家对象。我们还可以继续给中介者扩展更多功能,以适应游戏需求的不断变化。
假设我们正在编写一个手机购买的页面,在购买流程中,可以选择手机的颜色以及输入购买数量,同时页面中有两个展示区域,分别向用户展示刚刚选择好的颜色和数量。还有一个按钮动态显示下一步的操作,我们需要查询该颜色手机对应的库存,如果库存数量少于这次的购买数量,按钮将被禁用并且显示库存不足,反之按钮可以点击并且显示放入购物车。
初始状态:
// 初始手机库存
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>
接下来将分别监听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");
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 = "放入购物车";
};
虽然目前顺利完成了代码编写,但随之而来的需求改变有可能给我们带来麻烦。
假设现在要求去掉colorInfo
和numberInfo
这两个展示区域,我们就要分别改动colorSelect.onchange
和numberInput.onput
里面的代码,因为在先前的代码中,这些对象确实是耦合在一起的。
如何我们页面中将新增另外一个下拉选择框,代表选择手机内存。现在我们需要计算颜色、内存和购买数量,来判断nextBtn
是显示库存不足还是放入购物车。这个需求会再次改动许多的代码,这是因为在目前的实现中,每个节点对象都是耦合在一起的,改变或者增加任何一个节点对象,都要通知到与其相关的对象。
现在我们来引入中介者对象,所有的节点对象只跟中介者通信,当发生了事件行为时,仅仅通知中介者它们被改变了,同时把自身当作参数传入中介者,以便中介者辨别是谁发生了改变。剩下的所有事情都交给中介者对象来完成,这样一来,无论是修改还是新增节点,都只需要改动中介者对象里的代码。
选择颜色: <select id="colorSelect">
<option value="">请选择option>
<option value="red">红色option>
<option value="blue">蓝色option>
select>
选择内存: <select id="memorySelect">
<option value="">请选择option>
<option value="32G">32Goption>
<option value="16G">16Goption>
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);
};