《JavaScript設計模式與開發實踐》模式篇(11)—— 中介者模式

嗨呀豆豆呢發表於2018-12-23

中介者模式的作用就是解除物件與物件之間的緊耦合關係。增加一箇中介者物件後,所有的 相關物件都通過中介者物件來通訊,而不是互相引用,所以當一個物件發生改變時,只需要通知 中介者物件即可。中介者使各物件之間耦合鬆散,而且可以獨立地改變它們之間的互動。中介者模式使網狀的多對多關係變成了相對簡單的一對多關係

故事背景

假如在玩泡泡堂的遊戲,使用泡泡擊敗對方所有玩家才能獲得勝利。現在將隊伍分成兩個組進行遊戲

程式碼實現(未使用中介者模式)

/*玩家*/
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(){ // 玩家死亡 
    var all_dead = true;
    this.state = 'dead'; // 設定玩家狀態為死亡
    for ( var i = 0, partner; partner = this.partners[ i++ ]; ){ // 遍歷隊友列表
        if ( partner.state !== 'dead' ){ // 如果還有一個隊友沒有死亡,則遊戲還未失敗
            all_dead = false; break;
        } 
    }
    if( all_dead === true ){// 如果隊友全部死亡
        this.lose(); // 通知自己遊戲失敗
        for ( var i = 0, partner; partner = this.partners[ i++ ]; ){
            partner.lose(); 
        }
        for ( var i = 0, enemy; enemy = this.enemies[ i++ ]; ){ 
            enemy.win();
        } 
    }
};
/*定義一個工廠來建立玩家*/
var playerFactory = function( name, teamColor ){ 
    var newPlayer = new Player( name, teamColor );
    for ( var 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; 
};
/*建立玩家*/
//紅隊:
var player1 = playerFactory( '皮蛋', 'red' ),
    player2 = playerFactory( '小乖', 'red' ), 
    player3 = playerFactory( '寶寶', 'red' ), 
    player4 = playerFactory( '小強', 'red' );
//藍隊:
var player5 = playerFactory( '黑妞', 'blue' ),
    player6 = playerFactory( '蔥頭', 'blue' ), 
    player7 = playerFactory( '胖墩', 'blue' ), 
    player8 = playerFactory( '海盜', 'blue' );

讓紅隊玩家全部死亡:
player1.die(); 
player2.die();
player4.die();  
player3.die();
複製程式碼

執行結果

《JavaScript設計模式與開發實踐》模式篇(11)—— 中介者模式

重構思路

如果玩家不止8個,有成百上千個。一個玩家如果掉線或者更換隊伍,上面的程式碼完全無法解決。所以需要一箇中間物件去管理每個玩家之間的狀態與關係。如下圖所示

《JavaScript設計模式與開發實踐》模式篇(11)—— 中介者模式

程式碼重構(使用中介者模式)

  • 步驟
    • 定義Player建構函式和player物件的原型方法
    • 定義中介者物件playerDirector
    • 把操作轉交給中介者物件
/*******************玩家死亡*****************/
Player.prototype.die = function(){
    this.state = 'dead';
    playerDirector.reciveMessage( 'playerDead', this );// 給中介者傳送訊息,玩家死亡
};
/*******************移除玩家*****************/
Player.prototype.remove = function(){ // 給中介者傳送訊息,移除一個玩家
    playerDirector.reciveMessage('removePlayer', this );
};
/*******************玩家換隊*****************/
Player.prototype.changeTeam = function( color ){
    playerDirector.reciveMessage( 'changeTeam', this, color ); // 給中介者傳送訊息,玩家換隊
};
/*******************定義中介者物件*****************/
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
                    } 
                }
            } 
        }
    };

    var reciveMessage = function() {
        var message = Array.prototype.shift.call( arguments ); 
        operations[ message ].apply( this, arguments );
    };
    return {
        reciveMessage: reciveMessage
    } 
})();
/*******************設定工廠函式*****************/
var playerFactory = function( name, teamColor ){
    var newPlayer = new Player( name, teamColor ); // 創造一個新的玩家物件     
    playerDirector.reciveMessage( 'addPlayer', newPlayer ); // 給中介者傳送訊息,新增玩家
    return newPlayer; 
};
//紅隊
var player1 = playerFactory('皮蛋', 'red' ), 
    player2 = playerFactory('小乖', 'red' ),
    player3 = playerFactory( '寶寶', 'red' ), 
    player4 = playerFactory('小強', 'red' );
// 藍隊:
var player5 = playerFactory('黑妞', 'blue' ),
    player6 = playerFactory( '蔥頭', 'blue' ),  
    player7 = playerFactory( '胖墩', 'blue' ),
    player8 = playerFactory( '海盜', 'blue' );
player1.die(); 
player2.die();
player3.die(); 
player4.die();
複製程式碼

執行結果

《JavaScript設計模式與開發實踐》模式篇(11)—— 中介者模式
現在可以隨時的進行掉線或者換隊操作,玩家之間的耦合基本上消失了。

使用時機

中介者模式可以非常方便地對模組或者物件進行解耦,但物件之間並非一定需要解耦。在實際專案中,模組或物件之間有一些依賴關係是很正常的。畢竟我們寫程式是為了快速完成專案交付生產,而不是堆砌模式和過度設計。關鍵就在於如何去衡量物件之間的耦合程度。一般來說, 如果物件之間的複雜耦合確實導致呼叫和維護出現了困難,而且這些耦合度隨專案的變化呈指數增長曲線,那我們就可以考慮用中介者模式來重構程式碼。

小結

中介者模式是迎合迪米特法則的一種實現。迪米特法則也叫最少知識原則,是指一個物件應該儘可能少地瞭解另外的物件。如果物件之間的耦合性太高,一個物件 發生改變之後,難免會影響到其他的物件,而在中介者模式裡,物件之間幾乎不知道彼此的存在,它們只能通過中介者物件來互相影響對方。

因此,中介者模式使各個物件之間得以解耦,以中介者和物件之間的一對多關係取代了物件 之間的網狀多對多關係。各個物件只需關注自身功能的實現,物件之間的互動關係交給了中介者 物件來實現和維護。

不過,中介者模式也存在一些缺點。其中,最大的缺點是系統中會新增一箇中介者物件,因 為物件之間互動的複雜性,轉移成了中介者物件的複雜性,使得中介者物件經常是巨大的。中介 者物件自身往往就是一個難以維護的物件。

系列文章:

《JavaScript設計模式與開發實踐》最全知識點彙總大全

相關文章