《JavaScript設計模式與開發實踐》模式篇(11)—— 中介者模式
中介者模式的作用就是解除物件與物件之間的緊耦合關係。增加一箇中介者物件後,所有的 相關物件都通過中介者物件來通訊,而不是互相引用,所以當一個物件發生改變時,只需要通知 中介者物件即可。中介者使各物件之間耦合鬆散,而且可以獨立地改變它們之間的互動。中介者模式使網狀的多對多關係變成了相對簡單的一對多關係
故事背景
假如在玩泡泡堂的遊戲,使用泡泡擊敗對方所有玩家才能獲得勝利。現在將隊伍分成兩個組進行遊戲
程式碼實現(未使用中介者模式)
/*玩家*/
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();
複製程式碼
執行結果
重構思路
如果玩家不止8個,有成百上千個。一個玩家如果掉線或者更換隊伍,上面的程式碼完全無法解決。所以需要一箇中間物件去管理每個玩家之間的狀態與關係。如下圖所示
程式碼重構(使用中介者模式)
- 步驟
- 定義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設計模式與開發實踐》基礎篇(1)—— this、call 和 apply
《JavaScript設計模式與開發實踐》基礎篇(2)—— 閉包和高階函式
《JavaScript設計模式與開發實踐》模式篇(1)—— 單例模式
《JavaScript設計模式與開發實踐》模式篇(2)—— 策略模式
《JavaScript設計模式與開發實踐》模式篇(3)—— 代理模式
《JavaScript設計模式與開發實踐》模式篇(4)—— 迭代器模式
《JavaScript設計模式與開發實踐》模式篇(5)—— 觀察者模式
《JavaScript設計模式與開發實踐》模式篇(6)—— 命令模式
《JavaScript設計模式與開發實踐》模式篇(7)—— 組合模式
《JavaScript設計模式與開發實踐》模式篇(8)—— 模板方法模式
《JavaScript設計模式與開發實踐》模式篇(9)—— 享元模式