面向对象设计鼓励将行为分布到各个对象中,把对象划分成更小的粒度,有助于增强对象的可复用性,但由于这些细粒度对象之间的联系激增,又有可能会反过来降低它们的可复用性。
定义
中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。
# 案例:泡泡堂游戏
玩家分为两队对战,如果己方队伍的队员全部死亡,则这局游戏失败,同时敌人队伍的所有玩家都取得胜利。
# 一般思路实现
var players = [];
function Player(name, teamColor) {
this.name = name; //玩家名字
this.teamColor = teamColor; //队伍颜色
this.partners = []; //队友
this.enemies = []; //敌人
this.state = "live"; //玩家状态
}
//玩家团队胜利
Player.prototype.win = function () {
console.log("winner:" + this.name);
}
//玩家团队失败
Player.prototype.lose = function () {
console.log("loser:" + this.name);
}
//玩家死亡
Player.prototype.die = function () {
var allDie = true;
this.state = "dead";
console.log(this.name + "死亡");
for (var i = 0, partner; partner = this.partners[i++];) {
if (partner.state === "live") {
allDie = false;
break;
}
}
if (allDie) {
this.lose();
for (var i = 0, partner; partner = this.partners[i++];) {
partner.lose();
}
for (var i = 0, enemy; enemy = this.enemies[i++];) {
enemy.win();
}
}
}
//工厂模式创造玩家
function playerFactory(name, teamColor) {
//创建一个玩家对象
var newPlayer = new Player(name, teamColor);
//遍历所有玩家,互相添加队友和对手
for (var i = 0, player; player = players[i++];) {
if (player.teamColor === teamColor) {
player.partners.push(newPlayer);
newPlayer.partners.push(player);
} else {
player.enemies.push(newPlayer);
newPlayer.enemies.push(player);
}
}
//添加进所有玩家列表里
players.push(newPlayer);
console.log(name + "加入对战");
return newPlayer;
}
var player1 = playerFactory("玩家1", "red"),
player2 = playerFactory("玩家2", "red"),
player3 = playerFactory("玩家3", "red"),
player4 = playerFactory("玩家4", "red"),
player5 = playerFactory("玩家5", "red"),
player6 = playerFactory("玩家6", "blue"),
player7 = playerFactory("玩家7", "blue"),
player8 = playerFactory("玩家8", "blue"),
player9 = playerFactory("玩家9", "blue"),
player10 = playerFactory("玩家10", "blue");
player1.die();
player2.die();
player3.die();
player4.die();
player5.die();
以上代码实现可以随意的为游戏增加玩家或者队伍,但问题是,每个玩家和其他玩家都是紧耦合在一起的。每个玩家对象都有两个属性,this.partners和this.enemies,用来保存其他玩家对象的引用。当每个对象的状态发生改变,比如角色移动、吃到道具或者死亡时,都必须要显示地遍历通知其他玩家。
# 中介者模式重构
function Player (name,teamColor) {
this.name = name;
this.teamColor = teamColor;
this.state = "live";
}
Player.prototype.win = function () {
console.log("胜利:" + this.name);
}
Player.prototype.lose = function () {
console.log("失败:" + this.name);
}
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("playerChangeTeam", this, color);
}
function playerFactory (name, teamColor) {
var newPlayer = new Player(name, teamColor);
PlayerDirector.receiveMessage("addPlayer", newPlayer);
return newPlayer
}
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
}
}
}
}
}
};
在上面的代码中,除了中介者本身,没有一个玩家知道其他任何玩家的存在,玩家与玩家之间的耦合关系已经完全解除,某个玩家的任何操作都不需要通知其他玩家,而只需要给中介者发送一个消息,中介者处理完消息之后会把处理结果反馈给其他玩家对象。
# 中介者模式的优缺点
- 优点:中介者模式使各个对象之间得以解藕,以中介者和对象之间的一对多关系取代了对象之间的多对多关系。各个对象只需关注自身功能的实现,对象之间的交互关系交给了中介者对象来实现和维护。
- 缺点:系统中会新增一个中介者对象,对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个很难以维护的对象。
# 小结
中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则,是指一个对象应该尽可能少地了解另外的对象。
中介者模式可以非常方便地对模块或者对象进行解耦,但对象之间并非一定需要解藕。在实际项目中,模块或对象之间有一些依赖关系是很正常的。毕竟我们写程序是为了快速完成项目交付生产,而不是堆砌设计模式和过度设计。关键就在于如何去衡量对象之间的耦合程度。一般来说,如果对象之间的复杂耦合确实导致调用和维护出现了问题,而且这些耦合度随项目的变化呈指数增长曲线,那我们就可以考虑用中介者模式来重构代码。