设计模式[十一] 中介者模式

902 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

中介者模式的定义

在我们生活的世界中,每个人每个物体都会产生一些错综复杂的联系,在应用程序里也是一样,所有对象都按照某种关系和规则来通信

在程序里,也许一个对象会和其他10个对象打交道,当程序的规模增大,对象会越来越多,关系会越来越复杂。当我们想改变或者删除的时候,必须小心翼翼,很容易出问题

作用

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

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

1.png 看上图,如果对象A发生了改变,则需要同时通知其他所有对象

现实生活的例子

  1. 机场指挥塔

中介者也被称为调停者,我们想象一下机场的指挥塔,如果没有指挥塔的存在,每一架飞机要和方圆100公里内的所有飞机通信,才能确认航线以及飞行情况,后果是不可想象的。

现实中的情况是,每架飞机都只需要和指挥塔通信。从而知道飞行情况

  1. 菠菜公司

在世界杯期间购买足球彩票,如果没有菠菜公司作为中介,上千位的人一起计算赔率和输赢绝对是不可能实现的事情。有了菠菜公司作为中介,每个人只需要和菠菜公司发生关系,菠菜公司会根据所有人的投注情况计算和赔率,彩民们赢钱就从菠菜公司拿,输了钱就交给菠菜公司

例子——泡泡堂游戏

假如玩家的数目为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