中介者模式的作用就是解除对象与对象之间的紧耦合关系。所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。
中介者模式的例子–玩家对战游戏
玩家数目为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个节点:
- 下拉选择框colorSelect
- 文本输入框numberInput
- 展示颜色信息 colorInfo
- 展示购买数量信息numberInfo
- 决定下一步操作的按钮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对象即可。