Design Patterns [11] Mediator Pattern

Get into the habit of writing together! This is the third day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

Definition of the Mediator Pattern

In the world we live in, everyone and every object have some intricate connections, and the same is true in applications, where all objects communicate according to certain relationships and rules

In a program, maybe one object will deal with 10 other objects. When the scale of the program increases, there will be more and more objects, and the relationship will become more and more complicated. When we want to change or delete, we must be careful, it is easy to go wrong

effect

Release the tight coupling between objects. After adding a mediator object, all objects communicate through the mediator object instead of referencing each other, so when an object changes, only the mediator object needs to be notified.

The mediator object turns a web-like many-to-many relationship into a relatively simple one-to-many relationship

1.pngLooking at the picture above, if object A changes, all other objects need to be notified at the same time

real life example

  1. airport control tower

The intermediary is also called the mediator. Let us imagine the control tower of the airport. If there is no control tower, each aircraft must communicate with all aircraft within a radius of 100 kilometers to confirm the route and flight situation. The consequences are unimaginable. of.

The reality is that each aircraft only needs to communicate with the control tower. to know the flight situation

  1. Spinach Company

Buying football lottery tickets during the World Cup, if there is no gambling company as an intermediary, it is absolutely impossible for thousands of people to calculate the odds and win or lose. With the gambling company as an intermediary, everyone only needs to have a relationship with the gambling company. The gambling company will calculate the odds according to everyone's betting situation. The lottery players will get the money from the gambling company when they win, and give it to the gambling company if they lose.

Example - Bubble Hall Game

Suppose the number of players is 2, so when one of the players dies, the game ends and the teammates are notified of victory.


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()
}
复制代码

Next create 2 game objects:

const player1 = new Player('皮蛋')
const player2 = new Player('鸭子')
复制代码

Set enemies for each other

player1.enemy = player2
player2.enemy = player1
复制代码

When player 1 is killed by the bubble, just call this code

player1.die() // 输出:皮蛋lose、鸭子win
复制代码

Add team (without mediator mode)

Now let's improve the game. Changing the number of players to 8, setting teammates and enemies in the following way is undoubtedly inefficient

player1.partners = [player1, player2, player3, player4]
player1.enemies = [player5, player6, player7, player8]

player5.partners = [player5, player6, player7, player8]
player5.enemies = [player1, player2, player3, player4]
复制代码

Next, let's look at the normal code

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()
复制代码

Troubled by too many players

Now we can add players or teams at will, but the problem is that each player is tightly coupled to every other player.

In this example, only 8 players are created, and if in a large online game, there are hundreds or thousands of players on the screen, dozens of teams are fighting each other, if one player is disconnected, it must be removed from all other players' teammates Remove the player from the list. The game may also have the ability to ungroup and add to other teams, where red players can suddenly become blue players, and it's not just a problem that loops can solve. Faced with such a demand, our code above is not satisfactory.

Transforming Bubble Hall Game with Mediator Mode

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()
复制代码

Disadvantages of the mediator pattern

The biggest disadvantage is that a new mediator object will be added to the system, because the complexity of the interaction between objects is transferred to the complexity of the mediator object, making the mediator object often huge. The mediator object itself is often a hard-to-maintain object

For a complete record of design patterns, please see my github

Guess you like

Origin juejin.im/post/7082739823651323941