Skip to content

中介者模式(Mediator)

中介者模式使网状的多对多关系变成了相对简单的一对多关系

中介者模式的作用就是解除对象与对象之间的紧耦合关系

增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。

中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则,是指一个对象应该尽可能少地了解另外的对象。

优点

  • 解除对象与对象之间的紧耦合关系。

缺点

  • 需新增一个中介者对象,把对象之间的复杂性转为中介者对象的复杂性。使得中介者对象往往是巨大的,而且是一个难以维护的对象。

PS: 在实际开发中,很多时候对象之间并非需要解耦,有依赖关系很正常,但如果对象之间的复杂耦合确实导致调用和维护出现了困难,而且这些耦合度随项目的变化呈指数增长曲线,那就可以考虑使用中介者模式来重构代码。

对象间的耦合关系图

中介者模式

中介者模式

中介者模式

例子阐述模式:比如生活中的机场指挥塔,如果没有它的存在,每一架飞机都要实时和方圆几百公里的所有飞机通信,才能确定航线飞行状况。但有了机场指挥塔后,每架飞机只需要和指挥塔通信,它知道所有飞机的飞行状况,由它来安排所有飞机的起降时间和航线等。


:泡泡堂游戏(不适用中介者模式时)

现在有8个玩家,分两队,任意一队全部人员阵亡,则其他队获胜。

javascript
const players = []

function Player(name, teamColor) {
  this.partners = [] // 队友列表
  this.enemies = [] // 敌人列表
  this.state = 'live' // 玩家状态
  this.name = name // 角色名字
  this.teamColor = teamColor // 队伍颜色
}

// 团队胜利
Player.prototype.win = function () {
  console.log('winner:' + this.name)
}

// 团队失败
Player.prototype.lose = function () {
  console.log('loser:' + this.name)
}

// 玩家死亡
Player.prototype.die = function () {
  let all_dead = true
  this.state = 'dead' // 修改玩家状态

  // 遍历队友列表
  for (let i = 0, partner; partner = this.partners[i++];) {
    if (partner.state !== 'dead') {
      all_dead = false
      break
    }
  }

  // 如果全部队友死亡,通知所有队友失败,通知所有敌人胜利
  if (all_dead === true) {
    this.lose()
    for (let i = 0, partner; partner = this.partners[i++];) {
      partner.lose()
    }
    for (let i = 0, enemy; enemy = this.enemies[i++];) {
      enemy.win()
    }
  }
}

// 工厂函数
const playerFactory = function (name, teamColor) {
  const newPlayer = new Player(name, teamColor)

  // 通知所有玩家有新玩家加入
  for (let 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
}

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()

// 输出
// loser:小蛋
// loser:小皮
// loser:小怪
// loser:小宝
// winner:大皮
// winner:大怪
// winner:大宝
// winner:大蛋

从上面的代码可以看到,每个玩家都有两个属性 partnersenemies 用来保存其他玩家的引用,与其他玩家紧紧耦合在一起。每当玩家的状态发生改变,都需要显式遍历通知其他队伍,这无疑是一个巨大的工程。如果继续增加玩家,估计代码马上投降。

改造

使用中介者后,每个玩家无需再负责具体的执行逻辑,而是把操作交给中介者对象。

中介者对象一般有两种方式:

  1. 利用发布订阅模式,将中介者设置为订阅者,各个玩家为发布者,一旦玩家状态发生改变,就推送消息给中介者,然后中介者对象处理完消息后发送给其他玩家。
  2. 在中介者对象开发一些接收消息的接口,每个玩家都可以调用该接口来给中介者对象发送消息,接受一个参数用来识别发送者,然后中介者对象处理完消息后发生给其他玩家。

两种方式没有什么本质的区别,下例代码使用的是第二种。

javascript
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.ReceiveMessage('playerDead', this)
}

// 玩家退出
Player.prototype.remove = function () {
  playerDirector.ReceiveMessage('removePlayer', this)
}

// 玩家更换队伍
Player.prototype.changeTeam = function (color) {
  playerDirector.ReceiveMessage('changeTeam', this, color)
}

// 工厂函数
const playerFactory = function (name, teamColor) {
  const newPlayer = new Player(name, teamColor)
  playerDirector.ReceiveMessage('addPlayer', newPlayer)
  return newPlayer
}

// 中介者
const playerDirector = (function () {
  const players = {}, // 所有玩家
    operations = {} // 中介者的函数(操作)

  // 新增玩家
  operations.addPlayer = function (player) {
    const teamColor = player.teamColor
    players[teamColor] = players[teamColor] || []
    players[teamColor].push(player)
  }

  // 移除玩家
  operations.removePlayer = function (player) {
    const tempColor = player.teamColor,
      tempPlayers = players[tempColor] || []
    for (let i = tempPlayers.length - 1; i >= 0; i--) {
      if (tempPlayers[i] === player) {
        tempPlayers.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,
      teamPlayers = players[teamColor]
    let all_dead = true
    for (let i = 0, player; player = teamPlayers[i++];) {
      if (player.state !== 'dead') {
        all_dead = false;
        break;
      }
    }

    if (all_dead === true) {
      for (let i = 0, player; player = teamPlayers[i++];) {
        player.lose()
      }
      for (let color in players) {
        if (color !== teamColor) {
          const teamPlayers = players[color]
          for (let i = 0, player; player = teamPlayers[i++];) {
            player.win()
          }
        }
      }
    }
  }

  // 接收消息的接口
  const ReceiveMessage = function () {
    const message = Array.prototype.shift.call(arguments) // 第一个参数为消息名称
    operations[message].apply(this, arguments)
  }

  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()

// 输出
// loser:小蛋
// loser:小皮
// loser:小怪
// loser:小宝
// winner:大皮
// winner:大怪
// winner:大宝
// winner:大蛋

Released under the MIT License.