JavaScript設(shè)計模式-中介者模式

概念

??中介者模式的作用解除對象與對象之間的緊耦合關(guān)系,增加一個中介者對象后,所有的相關(guān)對象都通過中介者對象來通信,而不是互相引用,當一個對象發(fā)生改變時,只需要通知中介者對象即可。中介者使各對象之間耦合松散,而且可以獨立地改變它們之間的交互。中介者模式使網(wǎng)狀的多對多關(guān)系變成了相對簡單的一對多關(guān)系。

描述

??現(xiàn)實生活中有很多中介者的例子。如:在世界杯期間購買足球彩票,上千萬的人一起計算賠率和輸贏是不可能的,博彩公司作中介,每個人只需要與博彩公司發(fā)生關(guān)聯(lián),博彩公司會根據(jù)所有人的投注情況計算好賠率,彩民們贏了錢就從博彩公司拿,輸了錢就把錢交給博彩公司。

應(yīng)用

??我們來用JS模擬泡泡堂游戲。
【初始版本】
??Play.prototype.win、Play.prototype.lose表示玩家輸贏以及表示玩家死亡Play.prototype.die。因為玩家的數(shù)目是2,所以當其中一個玩家死亡的時候游戲便結(jié)束,同時通知它的對手勝利。

class Player {
    constructor(name) {
        this.name = name
        this.enemy = null
    }
    win() {
        console.log(this.name + ' won ')
    }
    lose() {
        console.log(this.name + ' lost')
    }
    die() {
        this.lose()
        this.enemy.win()
    }
}

??接下來創(chuàng)建2個玩家對象:

let player1 = new Player( 'hello' )
let player2 = new Player( 'world' )

??給玩家相互設(shè)置敵人:

player1.enemy = player2;
player2.enemy = player1;

??當玩家player1被泡泡炸死的時候,只需要調(diào)用這一句代碼便完成了一局游戲:

player1.die()

??上面代碼只是2個玩家,真正的泡泡堂游戲可以有8個玩家,并分成紅藍兩隊進行游戲。
??定義一個數(shù)組players來保存所有的玩家,在創(chuàng)建玩家之后,循環(huán)players來給每個玩家設(shè)置隊友和敵人:

let players = []

??為每個玩家對象都增加一些屬性,分別是隊友列表、敵人列表、玩家當前狀態(tài)、角色名字以及玩家所在的隊伍顏色:

class Player {
    constructor(name, teamColor) {
        this.partners = [] // 隊友列表
        this.enemies = [] // 敵人列表
        this.state = 'live' // 玩家狀態(tài)
        this.name = name // 角色名字
        this.teamColor = teamColor // 隊伍顏色
    }
    win() { // 玩家團隊勝利
        console.log( 'winner: ' + this.name )
    }
    lose() { // 玩家團隊失敗
        console.log( 'loser: ' + this.name )
    }
    die() { // 玩家死亡的方法要變得稍微復(fù)雜一點,需要在每個玩家死亡的時候,都遍歷其他隊友的生存狀況,
            // 如果隊友全部死亡,則這局游戲失敗,同時敵人隊伍的所有玩家都取得勝利
        let all_dead = true
        this.state = 'dead' // 設(shè)置玩家狀態(tài)為死亡
        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()
            }
        }
    }

}

??定義一個工廠來創(chuàng)建玩家:

let playerFactory = function( name, teamColor ){
    let newPlayer = new Player( name, teamColor ) // 創(chuàng)建新玩家
    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
}
//紅隊:
let player1 = playerFactory( '北京', 'red' ),
    player2 = playerFactory( '天津', 'red' ),
    player3 = playerFactory( '鄭州', 'red' ),
    player4 = playerFactory( '青島', 'red' )
//藍隊:
let player5 = playerFactory( '上海', 'blue' ),
    player6 = playerFactory( '深圳', 'blue' ),
    player7 = playerFactory( '杭州', 'blue' ),
    player8 = playerFactory( '廣州', 'blue' )

??現(xiàn)在已經(jīng)可以隨意地為游戲增加玩家或者隊伍,但問題是,每個玩家和其他玩家都是緊緊耦合在一起的。在此段代碼中,每個玩家對象都有兩個屬性,this.partnersthis.enemies,用來保存其他玩家對象的引用。當每個對象的狀態(tài)發(fā)生改變,比如角色移動、吃到道具或者死亡時,都必須要顯式地遍歷通知其他對象。
  如果在一個大型網(wǎng)絡(luò)游戲中,畫面里有成百上千個玩家,幾十支隊伍在互相廝殺。如果有一個玩家掉線,必須從所有其他玩家的隊友列表和敵人列表中都移除這個玩家。游戲也許還有解除隊伍和添加到別的隊伍的功能,紅色玩家可以突然變成藍色玩家,這就不再僅僅是循環(huán)能夠解決的問題了。

【中介者模式】
  現(xiàn)在開始用中介者模式來改造上面的泡泡堂游戲,首先仍然是定義Player構(gòu)造函數(shù)和player對象的原型方法,在player對象的這些原型方法中,不再負責具體的執(zhí)行邏輯,而是把操作轉(zhuǎn)交給中介者對象,把中介者對象命名為playerDirector:

class Player {
    constructor(name, teamColor) {
        this.name = name
        this.teamColor = teamColor
        this.state = 'live'
    }
    win() {
        console.log( this.name + ' won ' )
    }
    lose() {
        console.log( this.name +' lost' )
    }
    die() {
        this.state = 'dead'
        playerDirector.reciveMessage( 'playerDead', this ) // 給中介者發(fā)送消息,玩家死亡
    }
    remove() {
        playerDirector.reciveMessage( 'removePlayer', this ) // 給中介者發(fā)送消息,移除一個玩家
    }
    changeTeam(color) {
        playerDirector.reciveMessage( 'changeTeam', this, color ) // 給中介者發(fā)送消息,玩家換隊

    }
}

??再繼續(xù)改寫之前創(chuàng)建玩家對象的工廠函數(shù),可以看到,因為工廠函數(shù)里不再需要給創(chuàng)建的玩家對象設(shè)置隊友和敵人,這個工廠函數(shù)幾乎失去了工廠的意義:

let playerFactory=function(name,teamColor){
    let newPlayer=new Player(name,teamColor)   //創(chuàng)造一個新的玩家對象
    playerDirector.reciveMessage('addPlayer',newPlayer)    //給中介者發(fā)送消息,新增玩家
    return newPlayer
}

??playerDirector開放一個對外暴露的接口reciveMessage,負責接收player對象發(fā)送的消息,而player對象發(fā)送消息的時候,總是把自身this作為參數(shù)發(fā)送給playerDirector,以便playerDirector識別消息來自于哪個玩家對象。

let playerDirector= ( function(){
    let players = {}, // 保存所有玩家
        operations = {} // 中介者可以執(zhí)行的操作
    /****************新增一個玩家***************************/
    operations.addPlayer = function( player ){
        let teamColor = player.teamColor // 玩家的隊伍顏色
        players[ teamColor ] = players[ teamColor ] || [] // 如果該顏色的玩家還沒有成立隊伍,則新成立一個隊伍
        players[ teamColor ].push( player ) // 添加玩家進隊伍
    }
    /****************移除一個玩家***************************/
    operations.removePlayer = function( player ){
        let teamColor = player.teamColor, // 玩家的隊伍顏色
            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 ){ // 玩家死亡
        let 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() // 本隊所有玩家lose
            }
            for ( let color in players ){
                if ( color !== teamColor ){
                    let teamPlayers = players[ color ] // 其他隊伍的玩家
                    for ( let i = 0, player; player = teamPlayers[ i++ ]; ){
                        player.win() // 其他隊伍所有玩家win
                    }
                }
            }
        }
    }
    let reciveMessage = function(){
        let message = Array.prototype.shift.call( arguments ) // arguments 的第一個參數(shù)為消息名稱
        operations[ message ].apply( this, arguments )
    }

    return {
        reciveMessage: reciveMessage
    }
})()

??可以看到,除了中介者本身,沒有一個玩家知道其他任何玩家的存在,玩家與玩家之間的耦合關(guān)系已經(jīng)完全解除,某個玩家的任何操作都不需要通知其他玩家,而只需要給中介者發(fā)送一個消息,中介者處理完消息之后會把處理結(jié)果反饋給其他的玩家對象。還可以繼續(xù)給中介者擴展更多功能,以適應(yīng)游戲需求的不斷變化。

小結(jié)

??中介者模式符合迪米特法。迪米特法則也叫最少知識原則,是指一個對象應(yīng)該盡可能少地了解另外的對象。如果對象之間的耦合性太高,一個對象發(fā)生改變之后,難免會影響到其他的對象。而在中介者模式里,對象之間幾乎不知道彼此的存在,它們只能通過中介者對象來互相影響對方。因此,中介者模式使各個對象之間得以解耦,以中介者和對象之間的一對多關(guān)系取代了對象之間的網(wǎng)狀多對多關(guān)系。各個對象只需關(guān)注自身功能的實現(xiàn),對象之間的交互關(guān)系交給了中介者對象來實現(xiàn)和維護。不過,中介者模式也存在一些缺點。其中,最大的缺點是系統(tǒng)中會新增一個中介者對象,因為對象之間交互的復(fù)雜性,轉(zhuǎn)移成了中介者對象的復(fù)雜性,使得中介者對象經(jīng)常是巨大的。中介者對象自身往往就是一個難以維護的對象。

參考文獻

《JavaScript設(shè)計模式與開發(fā)實踐》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 中介者模式:通過中介者對象封裝一些列對象之間的交互,使對象之間不再相互引用,降低他們之間的耦合。 中介者和觀察者對...
    蟹老板愛寫代碼閱讀 690評論 1 0
  • javascript設(shè)計模式與開發(fā)實踐 設(shè)計模式 每個設(shè)計模式我們需要從三點問題入手: 定義 作用 用法與實現(xiàn) 單...
    穿牛仔褲的蚊子閱讀 4,489評論 0 13
  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實現(xiàn)同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 8,130評論 2 17
  • 接近凌晨這個城市依舊喧囂, 每個夜晚都有人喝了酒使勁嚷嚷, 反復(fù)反鎖的防盜門依舊給不了安全感 恐懼…… 這個被利益...
    等哈哈兒閱讀 133評論 0 0
  • 這天太冷了。 路晨壓低帽沿,裹緊大衣,急匆匆的走在街上。 夜已經(jīng)很深了,街兩旁的店鋪都已打烊,四周靜悄悄地沒有聲音...
    善下歸海閱讀 633評論 0 5

友情鏈接更多精彩內(nèi)容