面向对象设计鼓励将行为分布到各个对象中,把对象划分成更小的粒度,有助于增强对象的可复用性,但由于这些细粒度对象之间的联系激增,又有可能会反过来降低它们的可复用性。

定义

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。

# 案例:泡泡堂游戏

玩家分为两队对战,如果己方队伍的队员全部死亡,则这局游戏失败,同时敌人队伍的所有玩家都取得胜利。

# 一般思路实现

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

在上面的代码中,除了中介者本身,没有一个玩家知道其他任何玩家的存在,玩家与玩家之间的耦合关系已经完全解除,某个玩家的任何操作都不需要通知其他玩家,而只需要给中介者发送一个消息,中介者处理完消息之后会把处理结果反馈给其他玩家对象。

# 中介者模式的优缺点

  • 优点:中介者模式使各个对象之间得以解藕,以中介者和对象之间的一对多关系取代了对象之间的多对多关系。各个对象只需关注自身功能的实现,对象之间的交互关系交给了中介者对象来实现和维护。
  • 缺点:系统中会新增一个中介者对象,对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个很难以维护的对象。

# 小结

中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则,是指一个对象应该尽可能少地了解另外的对象。
中介者模式可以非常方便地对模块或者对象进行解耦,但对象之间并非一定需要解藕。在实际项目中,模块或对象之间有一些依赖关系是很正常的。毕竟我们写程序是为了快速完成项目交付生产,而不是堆砌设计模式和过度设计。关键就在于如何去衡量对象之间的耦合程度。一般来说,如果对象之间的复杂耦合确实导致调用和维护出现了问题,而且这些耦合度随项目的变化呈指数增长曲线,那我们就可以考虑用中介者模式来重构代码。