一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
中介者模式的定义
在我们生活的世界中,每个人每个物体都会产生一些错综复杂的联系,在应用程序里也是一样,所有对象都按照某种关系和规则来通信
在程序里,也许一个对象会和其他10个对象打交道,当程序的规模增大,对象会越来越多,关系会越来越复杂。当我们想改变或者删除的时候,必须小心翼翼,很容易出问题
作用
解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的对象都通过中介者对象来通信,而不是相互引用,所以当一个对象发生改变时,只需要通知中介者对象即可。
中介者对象使网状的多对多关系变成了相对简单的一对多关系
看上图,如果对象A发生了改变,则需要同时通知其他所有对象
现实生活的例子
- 机场指挥塔
中介者也被称为调停者,我们想象一下机场的指挥塔,如果没有指挥塔的存在,每一架飞机要和方圆100公里内的所有飞机通信,才能确认航线以及飞行情况,后果是不可想象的。
现实中的情况是,每架飞机都只需要和指挥塔通信。从而知道飞行情况
- 菠菜公司
在世界杯期间购买足球彩票,如果没有菠菜公司作为中介,上千位的人一起计算赔率和输赢绝对是不可能实现的事情。有了菠菜公司作为中介,每个人只需要和菠菜公司发生关系,菠菜公司会根据所有人的投注情况计算和赔率,彩民们赢钱就从菠菜公司拿,输了钱就交给菠菜公司
例子——泡泡堂游戏
假如玩家的数目为2,所以当其中一个玩家死亡的时候游戏结束,同时通知队友胜利。
function Player(name) {
this.name = name
this.enemy = null // 敌人
}
Player.prototype.win = function() {
console.log(`${this.name}win`)
}
Player.prototype.lose = function() {
console.log(`${this.name}lose`)
}
Player.prototype.die = function() {
this.lose()
this.enemy.win()
}
复制代码
接下来创建2个游戏对象:
const player1 = new Player('皮蛋')
const player2 = new Player('鸭子')
复制代码
给玩家相互设置敌人
player1.enemy = player2
player2.enemy = player1
复制代码
当玩家1被泡泡炸死的时候,只需要调用这一句代码即可
player1.die() // 输出:皮蛋lose、鸭子win
复制代码
增加队伍(不用中介者模式)
现在我们改进一下游戏。把玩家数量变为8个,用下面的方式设置队友和敌人无疑很低效
player1.partners = [player1, player2, player3, player4]
player1.enemies = [player5, player6, player7, player8]
player5.partners = [player5, player6, player7, player8]
player5.enemies = [player1, player2, player3, player4]
复制代码
接下来,让我们看看正常的代码
const players = []
class Player {
constructor(name, teamColor) {
this.partners = [] // 队友
this.enemies = [] // 敌人列表
this.state = 'live' // 玩家状态
this.name = name; // 玩家名字
this.teamColor = teamColor // 队伍颜色
}
win() {
console.log(`winner:${this.name}`);
}
lose() {
console.log(`loser:${this.name}`);
}
/**
* 玩家死亡的方法要稍微复杂点,我们在每个玩家死亡的时候,要遍历其他队友的生存情况,如果队友全部GG,则游戏结束
*/
die() {
this.state = 'dead' // 设置状态为死亡
// 如果队友全部死亡
const all_dead = this.partners.every(it => it.state === 'dead')
if (!all_dead) return
this.lose()
this.partners.forEach(it => it.lose())
this.enemies.forEach(it => it.win())
}
}
/**
* 最后定义一个工厂来创建玩家
*/
const playerFactory = function(name, teamColor) {
const newPlayer = new Player(name, teamColor)
players.forEach(player => {
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个角色
*/
// 红队
const player1 = playerFactory('皮蛋', 'red'),
player2 = playerFactory('小乖', 'red'),
player3 = playerFactory('宝宝', 'red'),
player4 = playerFactory('小强', 'red')
// 蓝队
const player5 = playerFactory('黑妞', 'blue'),
player6 = playerFactory('葱头', 'blue'),
player7 = playerFactory('胖墩', 'blue'),
player8 = playerFactory('海盗', 'blue')
/**
* 让红队玩家全部死亡
*/
player1.die()
player2.die()
player3.die()
player4.die()
复制代码
玩家过多带来的困扰
现在我们已经可以随意增加玩家或者队伍,但问题是,每个玩家和其他玩家都是紧密耦合的。
在这个例子中只创建了8个玩家,而如果在一个大型网游里,画面里有成百上千个玩家,几十支队伍互相厮杀,如果有一个玩家掉线,必须从所有其他玩家的队友列表中都移除这个玩家。游戏也许还有解除队伍和添加到别的队伍的功能,红色玩家可以突然变成蓝色玩家,这就不仅仅是循环能够解决的问题了。面对这样的需求,我们上面的代码可就差强人意了。
用中介者模式改造泡泡堂游戏
const players = []
class Player {
constructor(name, teamColor) {
this.state = 'live' // 玩家状态
this.name = name; // 玩家名字
this.teamColor = teamColor // 队伍颜色
}
win() {
console.log(`winner:${this.name}`);
}
lose() {
console.log(`loser:${this.name}`);
}
/**
* 玩家死亡
*/
die() {
this.state = 'dead' // 设置状态为死亡
playerDirector.receiveMessage('playerDead', this) // 给中介者发送消息,玩家死亡
}
/**
* 移除玩家
*/
remove() {
playerDirector.receiveMessage('removePlayer', this) // 给中介者发送消息,移除玩家
}
/**
* 切换队伍
*/
changeTeam(color) {
playerDirector.receiveMessage('changeTeam', this, color) // 给中介者发送消息,玩家换队
}
}
/**
* 在继续改写之前创建玩家对象的工厂函数,可以看到,因为工厂函数里不再需要给创建的玩家对象设置队友和敌人,这个工厂函数失去了工厂的意义:
*/
const playerFactory = function(name, teamColor) {
const newPlayer = new Player(name, teamColor) // 创建一个玩家
playerDirector.receiveMessage('addPlayer', newPlayer)// 给中介者发消息,新增玩家
return newPlayer
}
/**
* 最后,我们实现这个中介者playerDirector对象
*/
const playerDirector = (() => {
const players = {}, // 保存所有玩家
operations = {} // 中介者的操作
/**
* 新增一个玩家
*/
operations.addPlayer = function(player) {
const teamColor = player.teamColor // 玩家的队伍颜色
players[teamColor] = players[teamColor] || [] // 如果该颜色的玩家没有成立队伍,则新成立一个队伍
players[teamColor].push(player) // 添加玩家进队伍
}
/**
* 移除一个玩家
*/
operations.removePlayer = function(player) {
const teamColor = player.teamColor // 玩家的队伍颜色
const teamPlayers = players[teamColor] || [] // 该队伍所有成员
for(let 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) {
const teamColor = player.teamColor // 玩家的队伍颜色
const teamPlayers = players[teamColor]// 该队伍所有成员
const all_dead = teamPlayers.every(it => it.state === 'dead')
if (!all_dead) return
// 如果全局队友都死了,全部lose
teamPlayers.forEach(it => it.lose())
// 其他队伍的所有成员胜利
for(const color in players) {
// 同队的,返回
if (color === teamColor) continue
const teamPlayers = players[color] // 其他队伍的玩家
teamPlayers.forEach(it => it.win())
}
}
const receiveMessage = function(...args) {
const typeName = args.shift()
operations[typeName].apply(this, args)
}
return { receiveMessage }
})()
/**
* 可以看到,除了中介者本身,没有一个玩家知道其他任何玩家的存在,玩家与玩家之间的耦合关系已经消除,某个玩家的任何操作都不需要通知其他玩家,而只需要给中介发送一个消息,中介者处理完消息后会把处理结果反馈给其他玩家。我们还可以继续给中介者扩展更多功能,以适应游戏需求的不断变化
*/
// 我们来看下测试结果
// 红队
const player1 = playerFactory('皮蛋', 'red'),
player2 = playerFactory('小乖', 'red'),
player3 = playerFactory('宝宝', 'red'),
player4 = playerFactory('小强', 'red')
// 蓝队
const 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('blue')
player2.die()
player3.die()
player4.die()
复制代码
中介者模式的缺点
最大的缺点是系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往是一个难以维护的对象
设计模式完整记录请看我的github