javascript設計模式——中介者模式

来源:http://www.cnblogs.com/xiaohuochai/archive/2017/12/16/8042198.html
-Advertisement-
Play Games

前面的話 程式由大大小小的單一對象組成,所有這些對象都按照某種關係和規則來通信。當程式的規模增大,對象會越來越多,它們之間的關係也越來越複雜,難免會形成網狀的交叉引用。當改變或刪除其中一個對象的時候,很可能需要通知所有引用到它的對象。面向對象設計鼓勵將行為分佈到各個對象中,把對象劃分成更小的粒度,有 ...


前面的話

  程式由大大小小的單一對象組成,所有這些對象都按照某種關係和規則來通信。當程式的規模增大,對象會越來越多,它們之間的關係也越來越複雜,難免會形成網狀的交叉引用。當改變或刪除其中一個對象的時候,很可能需要通知所有引用到它的對象。面向對象設計鼓勵將行為分佈到各個對象中,把對象劃分成更小的粒度,有助於增強對象的可復用性,但由於這些細粒度對象之間的聯繫激增,又有可能會反過來降低它們的可復用性。中介者模式的作用就是解除對象與對象之間的緊耦合關係。增加一個中介者對象後,所有的相關對象都通過中介者對象來通信,而不是互相引用,所以當一個對象發生改變時,只需要通知中介者對象即可。中介者使各對象之間耦合鬆散,而且可以獨立地改變它們之間的交互。中介者模式使網狀的多對多關係變成了相對簡單的一對多關係。本文將詳細介紹中介者模式

 

泡泡堂

【初始版本】

  先定義一個玩家構造函數,它有3個簡單的原型方法:Play.prototype.win、Play.prototype.lose以及表示玩家死亡的Play.prototype.die。因為玩家的數目是2,所以當其中一個玩家死亡的時候游戲便結束,同時通知它的對手勝利。這段代碼看起來很簡單:

function Player( name ){
    this.name = name
    this.enemy = null; // 敵人
};

Player.prototype.win = function(){
    console.log( this.name + ' won ' );
};
Player.prototype.lose = function(){
    console.log( this.name +' lost' );
};
Player.prototype.die = function(){
    this.lose();
    this.enemy.win();
};

  接下來創建2個玩家對象:

//接下來創建2 個玩家對象:
var player1 = new Player( 'xiaohuochai' );
var player2 = new Player( 'match' );

  給玩家相互設置敵人:

//給玩家相互設置敵人:
player1.enemy = player2;
player2.enemy = player1;

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

player1.die();// 輸出:xiaohuochai lost、match won

  只有2個玩家其實沒什麼意思,真正的泡泡堂游戲至多可以有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];

  所以定義一個數組players來保存所有的玩家,在創建玩家之後,迴圈players來給每個玩家設置隊友和敵人:

var players=[];

  再改寫構造函數Player,使每個玩家對象都增加一些屬性,分別是隊友列表、敵人列表、玩家當前狀態、角色名字以及玩家所在的隊伍顏色:

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;
};

  現在用這段代碼創建8個玩家:

//紅隊:
var player1 = playerFactory( 'zhao', 'red' ),
player2 = playerFactory( 'qian', 'red' ),
player3 = playerFactory( 'sun', 'red' ),
player4 = playerFactory( 'li', 'red' );
//藍隊:
var player5 = playerFactory( 'zhou', 'blue' ),
player6 = playerFactory( 'wu', 'blue' ),
player7 = playerFactory( 'zheng', 'blue' ),
player8 = playerFactory( 'wang', 'blue' );

  現在已經可以隨意地為游戲增加玩家或者隊伍,但問題是,每個玩家和其他玩家都是緊緊耦合在一起的。在此段代碼中,每個玩家對象都有兩個屬性,this.partners和this.enemies,用來保存其他玩家對象的引用。當每個對象的狀態發生改變,比如角色移動、吃到道具或者死亡時,都必須要顯式地遍歷通知其他對象

  如果在一個大型網路游戲中,畫面里有成百上千個玩家,幾十支隊伍在互相廝殺。如果有一個玩家掉線,必須從所有其他玩家的隊友列表和敵人列表中都移除這個玩家。游戲也許還有解除隊伍和添加到別的隊伍的功能,紅色玩家可以突然變成藍色玩家,這就不再僅僅是迴圈能夠解決的問題了

【中介者模式】

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

function Player( name, teamColor ){
    this.name = name; // 角色名字
    this.teamColor = teamColor; // 隊伍顏色
    this.state = 'alive'; // 玩家生存狀態
};

Player.prototype.win = function(){
    console.log( this.name + ' won ' );
};

Player.prototype.lose = function(){
    console.log( this.name +' lost' );
};
/*******************玩家死亡*****************/
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 playerFactory=function(name,teamColor){
  var newPlayer=newPlayer(name,teamColor);    //創造一個新的玩家對象
  playerDirector.reciveMessage('addPlayer',newPlayer);    //給中介者發送消息,新增玩家
  return newPlayer;
};

  最後,需要實現這個中介者playerDirector對象,一般有以下兩種方式

  1、利用發佈—訂閱模式。將playerDirector實現為訂閱者,各player作為發佈者,一旦player的狀態發生改變,便推送消息給playerDirector,playerDirector處理消息後將反饋發送給其他player

  2、在playerDirector中開放一些接收消息的介面,各player可以直接調用該介面來給playerDirector發送消息,player只需傳遞一個參數給playerDirector,這個參數的目的是使playerDirector可以識別發送者。同樣,playerDirector接收到消息之後會將處理結果反饋給其他player

  這兩種方式的實現沒什麼本質上的區別。在這裡使用第二種方式,playerDirector開放一個對外暴露的介面reciveMessage,負責接收player對象發送的消息,而player對象發送消息的時候,總是把自身this作為參數發送給playerDirector,以便playerDirector識別消息來自於哪個玩家對象,代碼如下:

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 ); // arguments 的第一個參數為消息名稱
        operations[ message ].apply( this, arguments );
    };

    return {
        reciveMessage: reciveMessage
    }
})();

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

 

購買商品

  假設正在編寫一個手機購買的頁面,在購買流程中,可以選擇手機的顏色以及輸入購買數量,同時頁面中有兩個展示區域,分別向用戶展示剛剛選擇好的顏色和數量。還有一個按鈕動態顯示下一步的操作,需要查詢該顏色手機對應的庫存,如果庫存數量少於這次的購買數量,按鈕將被禁用並且顯示庫存不足,反之按鈕可以點擊並且顯示放入購物車

  這個需求是非常容易實現的,假設已經提前從後臺獲取到了所有顏色手機的庫存量:

var goods ={    //手機庫存
  "red":3,
  "blue":6
};

  那麼頁面有可能顯示為如下幾種場景:

  1、選擇紅色手機,購買4個,庫存不足

  2、選擇藍色手機,購買5個,庫存充足,可以加入購物車

  3、或者是沒有輸入購買數量的時候,按鈕將被禁用並顯示相應提示

  接下來將遇到至少5個節點,分別是:

  1、下拉選擇框colorSelect

  2、文本輸入框numberInput

  3、展示顏色信息colorInfo

  4、展示購買數量信息numberInfo

  5、決定下一步操作的按鈕nextBtn

【基礎版本】

  從編寫HTML代碼開始

<body>
選擇顏色: <select id="colorSelect">
  <option value="">請選擇</option>
  <option value="red">紅色</option>
  <option value="blue">藍色</option>
</select>
輸入購買數量: <input type="text" id="numberInput"/>
您選擇了顏色: <div id="colorInfo"></div><br/>
您輸入了數量: <div id="numberInfo"></div><br/>
<button id="nextBtn" disabled="true">請選擇手機顏色和購買數量</button>
</body>

  接下來將分別監聽colorSelect的onchange事件函數和numberInput的oninput事件函數,然後在這兩個事件中作出相應處理

<script>
  var colorSelect = document.getElementById( 'colorSelect' ), 
      numberInput = document.getElementById( 'numberInput' ), 
      colorInfo = document.getElementById( 'colorInfo' ), 
      numberInfo = document.getElementById( 'numberInfo' ), 
      nextBtn = document.getElementById( 'nextBtn' );
  var goods = {    // 手機庫存
    "red": 3,
    "blue": 6
  };
  colorSelect.onchange = function(){ 
    var color = this.value,    // 顏色
        number = numberInput.value,    // 數量
        stock = goods[ color ];    // 該顏色手機對應的當前庫存
    colorInfo.innerHTML = color;
    if ( !color ){ 
      nextBtn.disabled = true;
      nextBtn.innerHTML = '請選擇手機顏色'; 
      return;
    }
    if ( ( ( number - 0 ) | 0 ) !== number - 0 ){    // 用戶輸入的購買數量是否為正整數
      nextBtn.disabled = true;
      nextBtn.innerHTML = '請輸入正確的購買數量'; return;
    }
    if ( number > stock ){    // 當前選擇數量沒有超過庫存量
      nextBtn.disabled = true; 
      nextBtn.innerHTML = '庫存不足'; 
      return ;
    }
    nextBtn.disabled = false; 
    nextBtn.innerHTML = '放入購物車';
};
</script>

  當觸發了colorSelect的onchange之後,首先要讓colorInfo中顯示當前選中的顏色,然後獲取用戶當前輸入的購買數量,對用戶的輸入值進行一些合法性判斷。再根據庫存數量來判斷nextBtn的顯示狀態

  接下來,編寫numberInput的事件相關代碼:

numberInput.oninput = function(){
  var color = colorSelect.value,    // 顏色
      number = this.value,    // 數量
      stock = goods[ color ];    // 該顏色手機對應的當前庫存
  numberInfo.innerHTML = number; 
  if ( !color ){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '請選擇手機顏色'; 
    return;
  }

  if ( ( ( number - 0 ) | 0 ) !== number - 0 ){    // 輸入購買數量是否為正整數
    nextBtn.disabled = true;
    nextBtn.innerHTML = '請輸入正確的購買數量'; 
    return;
  }
  if ( number > stock ){    // 當前選擇數量沒有超過庫存量
      nextBtn.disabled = true; 
      nextBtn.innerHTML = '庫存不足'; 
      return ;
  }
  nextBtn.disabled = false; 
  nextBtn.innerHTML = '放入購物車';
};

  雖然目前順利完成了代碼編寫,但隨之而來的需求改變有可能給我們帶來麻煩。假設現在要求去掉colorInfo和numberInfo這兩個展示區域,就要分別改動colorSelect.onchange和numberInput.onput裡面的代碼,因為在先前的代碼中,這些對象確實是耦合在一起的

  當這個頁面里的節點激增到10個或者15個時,它們之間的聯繫可能變得更加錯綜複雜,任何一次改動都將變得很棘手。為了證實這一點,假設頁面中將新增另外一個下拉選擇框,代表選擇手機記憶體。現在需要計算顏色、記憶體和購買數量,來判斷nextBtn是顯示庫存不足還是放入購物車

  首先要增加兩個HTML節點:

<body>
  選擇顏色:    <select id="colorSelect">
  <option value="">請選擇</option>
  <option value="red">紅色</option>
  <option value="blue">藍色</option>
  </select>
  選擇記憶體:    <select id="memorySelect">
  <option value="">請選擇</option>
  <option value="32G">32G</option>
  <option value="16G">16G</option>
  </select>
  輸入購買數量: <input type="text" id="numberInput"/><br/>
  您選擇了顏色: <div id="colorInfo"></div><br/> 
  您選擇了記憶體: <div id="memoryInfo"></div><br/> 
  您輸入了數量: <div id="numberInfo"></div><br/>
  <button id="nextBtn" disabled="true">請選擇手機顏色和購買數量</button>
</body>

<script>
  var colorSelect = document.getElementById( 'colorSelect' ),
      numberInput = document.getElementById( 'numberInput' ), 
      memorySelect = document.getElementById( 'memorySelect' ), 
      colorInfo = document.getElementById( 'colorInfo' ), 
      numberInfo = document.getElementById( 'numberInfo' ), 
      memoryInfo = document.getElementById( 'memoryInfo' ), 
      nextBtn = document.getElementById( 'nextBtn' );
</script>

  接下來修改表示存庫的JSON對象以及修改colorSelect的onchange事件函數:

<script>
  var goods = {    // 手機庫存
    "red|32G": 3,    // 紅色 32G,庫存數量為 3
    "red|16G": 0,
    "blue|32G": 1,
    "blue|16G": 6
  };

  colorSelect.onchange = function(){ 
    var color = this.value,
        memory = memorySelect.value,
        stock = goods[ color + '|' + memory ];
    number = numberInput.value,    // 數量
    colorInfo.innerHTML = color;

    if ( !color ){ 
      nextBtn.disabled = true;
      nextBtn.innerHTML = '請選擇手機顏色';
      return;
    }
    if ( !memory ){ 
      nextBtn.disabled = true;
      nextBtn.innerHTML = '請選擇記憶體大小'; 
      return;
    }
    if ( ( ( number - 0 ) | 0 ) !== number - 0 ){    // 輸入購買數量是否為正整數
      nextBtn.disabled = true;
      nextBtn.innerHTML = '請輸入正確的購買數量'; 
      return;
    }
    if ( number > stock ){    // 當前選擇數量沒有超過庫存量
      nextBtn.disabled = true; 
      nextBtn.innerHTML = '庫存不足'; 
      return ;
    }
    nextBtn.disabled = false; 
    nextBtn.innerHTML = '放入購物車';
  };
</script>

  當然同樣要改寫numberInput的事件相關代碼,具體代碼的改變跟colorSelect大同小異。最後還要新增memorySelect的onchange事件函數:

<script>
  memorySelect.onchange = function(){
    var color = colorSelect.value,    // 顏色 
    number = numberInput.value,    // 數量 
    memory = this.value,
    stock = goods[ color + '|' + memory ];    // 該顏色手機對應的當前庫存
    memoryInfo.innerHTML = memory;
    if ( !color ){ 
      nextBtn.disabled = true;
      nextBtn.innerHTML = '請選擇手機顏色'; 
      return;
    }
    if ( !memory ){ 
      nextBtn.disabled = true;
      nextBtn.innerHTML = '請選擇記憶體大小';
      return;
    }
    if ( ( ( number - 0 ) | 0 ) !== number - 0 ){    // 輸入購買數量是否為正整數
      nextBtn.disabled = true;
      nextBtn.innerHTML = '請輸入正確的購買數量'; 
      return;
    }
    if ( number > stock ){    // 當前選擇數量沒有超過庫存量
      nextBtn.disabled = true; 
      nextBtn.innerHTML = '庫存不足'; 
      return ;
    }
    nextBtn.disabled = false; 
    nextBtn.innerHTML = '放入購物車';
  };
</script>

  僅僅是增加一個記憶體的選擇條件,就要改變如此多的代碼,這是因為在目前的實現中,每個節點對象都是耦合在一起的,改變或者增加任何一個節點對象,都要通知到與其相關的對象

【引入中介者】

  現在來引入中介者對象,所有的節點對象只跟中介者通信。當下拉選擇框colorSelect、memorySelect和文本輸入框numberInput發生了事件行為時,它們僅僅通知中介者它們被改變了,同時把自身當作參數傳入中介者,以便中介者辨別是誰發生了改變。剩下的所有事情都交給中介者對象來完成,這樣一來,無論是修改還是新增節點,都只需要改動中介者對象里的代碼

var goods = {    // 手機庫存
  "red|32G": 3,
  "red|16G": 0,
  "blue|32G": 1,
  "blue|16G": 6
};
var mediator = (function(){

  var colorSelect = document.getElementById( 'colorSelect' ), 
      memorySelect = document.getElementById( 'memorySelect' ), 
      numberInput = document.getElementById( 'numberInput' ), 
      colorInfo = document.getElementById( 'colorInfo' ), 
      memoryInfo = document.getElementById( 'memoryInfo' ), 
      numberInfo = document.getElementById( 'numberInfo' ), 
      nextBtn = document.getElementById( 'nextBtn' );
  return {
    changed: function( obj ){
      var color = colorSelect.value,    // 顏色 
      memory = memorySelect.value,// 記憶體 
      number = numberInput.value,        // 數量
      stock = goods[ color + '|' + memory ];    // 顏色和記憶體對應的手機庫存數量

      if ( obj === colorSelect ){    // 如果改變的是選擇顏色下拉框
        colorInfo.innerHTML = color;
      }else if ( obj === memorySelect ){ 
        memoryInfo.innerHTML = memory;
      }else if ( obj === numberInput ){
        numberInfo.innerHTML = number;
      }
      if ( !color ){ 
        nextBtn.disabled = true;
        nextBtn.innerHTML = '請選擇手機顏色';
        return;
      }
      if ( !memory ){ 
        nextBtn.disabled = true;
        nextBtn.innerHTML = '請選擇記憶體大小';
        return;
      }
      if ( ( ( number - 0 ) | 0 ) !== number - 0 ){    // 輸入購買數量是否為正整數
        nextBtn.disabled = true;
        nextBtn.innerHTML = '請輸入正確的購買數量'; 
        return;
      }
      nextBtn.disabled = false; 
      nextBtn.innerHTML = '放入購物車';
    }
  }
})();

// 事件函數:
colorSelect.onchange = function(){ 
  mediator.changed( this );
};
memorySelect.onchange = function(){ 
  mediator.changed( this );
};
numberInput.oninput = function(){ 
  mediator.changed( this );
};

  可以想象,某天又要新增一些跟需求相關的節點,比如CPU型號,那隻需要稍稍改動mediator對象即可:

var goods = {    // 手機庫存
  "red|32G|800": 3,    // 顏色 red,記憶體 32G,cpu800,對應庫存數量為 3 
  "red|16G|801": 0,
  "blue|32G|800": 1,
  "blue|16G|801": 6
};

var mediator = (function(){
  //
  var cpuSelect = document.getElementById( 'cpuSelect' );
  return {
    change: function(obj){
      //
      var cpu = cpuSelect.value,
      stock = goods[ color + '|' + memory + '|' + cpu ];
      if ( obj === cpuSelect ){ 
        cpuInfo.innerHTML = cpu;
      }
    }
  }
})();

 

總結

  中介者模式是迎合迪米特法則的一種實現。迪米特法則也叫最少知識原則,是指一個對象應該儘可能少地瞭解另外的對象。如果對象之間的耦合性太高,一個對象發生改變之後,難免會影響到其他的對象。而在中介者模式里,對象之間幾乎不知道彼此的存在,它們只能通過中介者對象來互相影響對方。因此,中介者模式使各個對象之間得以解耦,以中介者和對象之間的一對多關係取代了對象之間的網狀多對多關係。各個對象只需關註自身功能的實現,對象之間的交互關係交給了中介者對象來實現和維護

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

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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前些日子辭掉了工作,比較輕鬆,有大把的時間寫博客神馬的,陸續面了幾個不錯的互聯網公司,有成功的也有失敗的,阿裡的面試及offer都來的很意外,還沒來得及投百度,由於阿裡給的條件及工作內容都讓我很滿意了,唯一的遺憾是得離開北京去杭州,不知道現在還要不要投投其他的也試試。 至於為什麼選在年前離職,其實這 ...
  • 公式: 1)y=ax^2+bx+c; 2) y=a(x-k)+h; 通過已知三點可確定a,b,c,h,k 2.通過圓心坐標(a,b)和半徑r可確定一個圓,和已知的x坐標: 公式: 1)r^2=(x-a)^2+(y-b)^2 3.圓是否相交,可通過求得圓心距G確定,圓心(x1,y1)和(x2,y2): ...
  • Examples 我是第一行文字刪除 我是第一行文字刪除 我是第一行文字刪除 我是第一行文字刪除 我是第一行文字刪除 我是第一行文字刪除 ...
  • Examples 分享到 ...
  • 調出js控制台可以在瀏覽器地址欄輸入about:blank,如果不輸入about:blank,直接 打開一個新的頁面,有可能輸出的結果不准確。也就是說變數有可能被其他的影響到, 造成結果不准確。 ...
  • 1.屬性的簡潔表示法 function f(x,y) { return {x,y}; } // 等同於 function f(x,y){ return {x:x,y:y}; } f(1,2) // Object {x:1,y:2} 例如: let birth = '2000/01/01'; cons ...
  • 前言 頁面顯示到瀏覽器上的過程: 1.1、生成一個DOM樹。 瀏覽器將獲取到的HTML代碼解析成1個DOM樹,包含了所有標簽,包括display:none和動態添加的節點。 1.2、生成樣式結構體。 瀏覽器將所有樣式解析成樣式結構體,解析過程中會去掉瀏覽器不能識別的。 2、DOM樹和樣式結構體結合生 ...
  • 最近這個月一直在趕項目開發,遇到的問題和學到的前端知識沒有更新到博客園,現在閑了下來,就整理一下前端知識。 在項目開發中,在樣式這方面花費的時間較多,因為針對於數字的變化特別多,本人不愛記數字,在看設計圖時總是反覆計算之間的數值,覺得很麻煩,偶然谷歌一下,發現了css3的一個屬性--calc() c ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...