先來理解這么一個問題,假如我們前端開發接的需求是需求方給我們需求,可能一個前端開發會和多個需求方打交道,所以會保持多個需求方的聯系,那么在程序里面就意味著保持多個對象的引用,當程序的規模越大,對象會越來越多,他們之間的關系會越來越復雜,那現在假如現在有一個中介者(假如就是我們的主管)來對接多個需求方的需求,那么需求方只需要把所有的需求給我們主管就可以,主管會依次看我們的工作量來給我們分配任務,這樣的話,我們前端開發就不需要和多個業務方聯系,我們只需要和我們主管(也就是中介)聯系即可,這樣的好處就弱化了對象之間的耦合。
日常生活中的列子:
中介者模式對于我們日常生活中經常會碰到,比如我們去房屋中介去租房,房屋中介人在租房者和房東出租者之間形成一條中介;租房者并不關心租誰的房,房東出租者也并不關心它租給誰,因為有中介,所以需要中介來完成這場交易。
中介者模式的作用是解除對象與對象之間的耦合關系,增加一個中介對象后,所有的相關對象都通過中介者對象來通信,而不是相互引用,所以當一個對象發送改變時,只需要通知中介者對象即可。中介者使各個對象之間耦合松散,而且可以獨立地改變它們之間的交互。
實現中介者的列子如下:
不知道大家有沒有玩過英雄殺這個游戲,最早的時候,英雄殺有2個人(分別是敵人和自己);我們針對這個游戲先使用普通的函數來實現如下:
比如先定義一個函數,該函數有三個方法,分別是win(贏), lose(輸),和die(敵人死亡)這三個函數;只要一個玩家死亡該游戲就結束了,同時需要通知它的對手勝利了; 代碼需要編寫如下:
~~~
function Hero(name) {
this.name = name;
this.enemy = null;
}
Hero.prototype.win = function(){
console.log(this.name + 'Won');
}
Hero.prototype.lose = function(){
console.log(this.name + 'lose');
}
Hero.prototype.die = function(){
this.lose();
this.enemy.win();
}
// 初始化2個對象
var h1 = new Hero("朱元璋");
var h2 = new Hero("劉伯溫");
// 給玩家設置敵人
h1.enemy = h2;
h2.enemy = h1;
// 朱元璋死了 也就輸了
h1.die(); // 輸出 朱元璋lose 劉伯溫Won
~~~
現在我們再來為游戲添加隊友
比如現在我們來為游戲添加隊友,比如英雄殺有6人一組,那么這種情況下就有隊友,敵人也有3個;因此我們需要區分是敵人還是隊友需要隊的顏色這個字段,如果隊的顏色相同的話,那么就是同一個隊的,否則的話就是敵人;
我們可以先定義一個數組players來保存所有的玩家,在創建玩家之后,循環players來給每個玩家設置隊友或者敵人;
var players = [];
接著我們再來編寫Hero這個函數;代碼如下:
~~~
var players = []; // 定義一個數組 保存所有的玩家
function Hero(name,teamColor) {
this.friends = []; //保存隊友列表
this.enemies = []; // 保存敵人列表
this.state = 'live'; // 玩家狀態
this.name = name; // 角色名字
this.teamColor = teamColor; // 隊伍的顏色
}
Hero.prototype.win = function(){
// 贏了
console.log("win:" + this.name);
};
Hero.prototype.lose = function(){
// 輸了
console.log("lose:" + this.name);
};
Hero.prototype.die = function(){
// 所有隊友死亡情況 默認都是活著的
var all_dead = true;
this.state = 'dead'; // 設置玩家狀態為死亡
for(var i = 0,ilen = this.friends.length; i < ilen; i+=1) {
// 遍歷,如果還有一個隊友沒有死亡的話,則游戲還未結束
if(this.friends[i].state !== 'dead') {
all_dead = false;
break;
}
}
if(all_dead) {
this.lose(); // 隊友全部死亡,游戲結束
// 循環 通知所有的玩家 游戲失敗
for(var j = 0,jlen = this.friends.length; j < jlen; j+=1) {
this.friends[j].lose();
}
// 通知所有敵人游戲勝利
for(var j = 0,jlen = this.enemies.length; j < jlen; j+=1) {
this.enemies[j].win();
}
}
}
// 定義一個工廠類來創建玩家
var heroFactory = function(name,teamColor) {
var newPlayer = new Hero(name,teamColor);
for(var i = 0,ilen = players.length; i < ilen; i+=1) {
// 如果是同一隊的玩家
if(players[i].teamColor === newPlayer.teamColor) {
// 相互添加隊友列表
players[i].friends.push(newPlayer);
newPlayer.friends.push(players[i]);
}else {
// 相互添加到敵人列表
players[i].enemies.push(newPlayer);
newPlayer.enemies.push(players[i]);
}
}
players.push(newPlayer);
return newPlayer;
};
// 紅隊
var p1 = heroFactory("aa",'red'),
p2 = heroFactory("bb",'red'),
p3 = heroFactory("cc",'red'),
p4 = heroFactory("dd",'red');
// 藍隊
var p5 = heroFactory("ee",'blue'),
p6 = heroFactory("ff",'blue'),
p7 = heroFactory("gg",'blue'),
p8 = heroFactory("hh",'blue');
// 讓紅隊玩家全部死亡
p1.die();
p2.die();
p3.die();
p4.die();
// lose:dd lose:aa lose:bb lose:cc
// win:ee win:ff win:gg win:hh
~~~
如上代碼:Hero函數有2個參數,分別是name(玩家名字)和teamColor(隊顏色),
首先我們可以根據隊顏色來判斷是隊友還是敵人;同樣也有三個方法win(贏),lose(輸),和die(死亡);如果每次死亡一個人的時候,循環下該死亡的隊友有沒有全部死亡,如果全部死亡了的話,就輸了,因此需要循環他們的隊友,分別告訴每個隊友中的成員他們輸了,同時需要循環他們的敵人,分別告訴他們的敵人他們贏了;因此每次死了一個人的時候,都需要循環一次判斷他的隊友是否都死亡了;因此每個玩家和其他的玩家都是緊緊耦合在一起了。
下面我們可以使用中介者模式來改善上面的demo;
首先我們仍然定義Hero構造函數和Hero對象原型的方法,在Hero對象的這些原型方法中,不再負責具體的執行的邏輯,而是把操作轉交給中介者對象,中介者對象來負責做具體的事情,我們可以把中介者對象命名為playerDirector;
在playerDirector開放一個對外暴露的接口ReceiveMessage,負責接收player對象發送的消息,而player對象發送消息的時候,總是把自身的this作為參數發送給playerDirector,以便playerDirector 識別消息來自于那個玩家對象。
代碼如下:
~~~
var players = []; // 定義一個數組 保存所有的玩家
function Hero(name,teamColor) {
this.state = 'live'; // 玩家狀態
this.name = name; // 角色名字
this.teamColor = teamColor; // 隊伍的顏色
}
Hero.prototype.win = function(){
// 贏了
console.log("win:" + this.name);
};
Hero.prototype.lose = function(){
// 輸了
console.log("lose:" + this.name);
};
// 死亡
Hero.prototype.die = function(){
this.state = 'dead';
// 給中介者發送消息,玩家死亡
playerDirector.ReceiveMessage('playerDead',this);
}
// 移除玩家
Hero.prototype.remove = function(){
// 給中介者發送一個消息,移除一個玩家
playerDirector.ReceiveMessage('removePlayer',this);
};
// 玩家換隊
Hero.prototype.changeTeam = function(color) {
// 給中介者發送一個消息,玩家換隊
playerDirector.ReceiveMessage('changeTeam',this,color);
};
// 定義一個工廠類來創建玩家
var heroFactory = function(name,teamColor) {
// 創建一個新的玩家對象
var newHero = new Hero(name,teamColor);
// 給中介者發送消息,新增玩家
playerDirector.ReceiveMessage('addPlayer',newHero);
return newHero;
};
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;
}
}
// 如果all_dead 為true的話 說明全部死亡
if(all_dead) {
for(var i = 0, player; player = teamPlayers[i++]; ) {
// 本隊所有玩家lose
player.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 ReceiveMessage = function(){
// arguments的第一個參數為消息名稱 獲取第一個參數
var message = Array.prototype.shift.call(arguments);
operations[message].apply(this,arguments);
};
return {
ReceiveMessage : ReceiveMessage
};
})();
// 紅隊
var p1 = heroFactory("aa",'red'),
p2 = heroFactory("bb",'red'),
p3 = heroFactory("cc",'red'),
p4 = heroFactory("dd",'red');
// 藍隊
var p5 = heroFactory("ee",'blue'),
p6 = heroFactory("ff",'blue'),
p7 = heroFactory("gg",'blue'),
p8 = heroFactory("hh",'blue');
// 讓紅隊玩家全部死亡
p1.die();
p2.die();
p3.die();
p4.die();
// lose:aa lose:bb lose:cc lose:dd
// win:ee win:ff win:gg win:hh
~~~
我們可以看到如上代碼;玩家與玩家之間的耦合代碼已經解除了,而把所有的邏輯操作放在中介者對象里面進去處理,某個玩家的任何操作不需要去遍歷去通知其他玩家,而只是需要給中介者發送一個消息即可,中介者接受到該消息后進行處理,處理完消息之后會把處理結果反饋給其他的玩家對象。使用中介者模式解除了對象與對象之間的耦合代碼; 使程序更加的靈活.
中介者模式實現購買商品的列子
下面的列子是書上的列子,比如在淘寶或者天貓的列子不是這樣實現的,也沒有關系,我們可以改動下即可,我們最主要來學習下使用中介者模式來實現的思路。
首先先介紹一下業務:在購買流程中,可以選擇手機的顏色以及輸入購買的數量,同時頁面中有2個展示區域,分別顯示用戶剛剛選擇好的顏色和數量。還有一個按鈕動態顯示下一步的操作,我們需要查詢該顏色手機對應的庫存,如果庫存數量小于這次的購買數量,按鈕則被禁用并且顯示庫存不足的文案,反之按鈕高亮且可以點擊并且顯示假如購物車。
HTML代碼如下:
選擇顏色:
~~~
<select id="colorSelect">
<option value="">請選擇</option>
<option value="red">紅色</option>
<option value="blue">藍色</option>
</select>
<p>輸入購買的數量: <input type="text" id="numberInput"/></p>
你選擇了的顏色:<div id="colorInfo"></div>
<p>你輸入的數量: <div id="numberInfo"></div> </p>
<button id="nextBtn" disabled="true">請選擇手機顏色和購買數量</button>
~~~
首先頁面上有一個select選擇框,然后有輸入的購買數量輸入框,還有2個展示區域,分別是選擇的顏色和輸入的數量的顯示的區域,還有下一步的按鈕操作;
我們先定義一下:
假設我們提前從后臺獲取到所有顏色手機的庫存量
~~~
var goods = {
// 手機庫存
"red": 6,
"blue": 8
};
~~~
接著 我們下面分別來監聽colorSelect的下拉框的onchange事件和numberInput輸入框的oninput的事件,然后在這兩個事件中作出相應的處理
常規的JS代碼如下:
~~~
// 假設我們提前從后臺獲取到所有顏色手機的庫存量
var goods = {
// 手機庫存
"red": 6,
"blue": 8
};
/*
我們下面分別來監聽colorSelect的下拉框的onchange事件和numberInput輸入框的oninput的事件,
然后在這兩個事件中作出相應的處理
*/
var colorSelect = document.getElementById("colorSelect"),
numberInput = document.getElementById("numberInput"),
colorInfo = document.getElementById("colorInfo"),
numberInfo = document.getElementById("numberInfo"),
nextBtn = document.getElementById("nextBtn");
// 監聽change事件
colorSelect.onchange = function(e){
select();
};
numberInput.oninput = function(){
select();
};
function select(){
var color = colorSelect.value, // 顏色
number = numberInput.value, // 數量
stock = goods[color]; // 該顏色手機對應的當前庫存
colorInfo.innerHTML = color;
numberInfo.innerHTML = number;
// 如果用戶沒有選擇顏色的話,禁用按鈕
if(!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = "請選擇手機顏色";
return;
}
// 判斷用戶輸入的購買數量是否是正整數
var reg = /^\d+$/g;
if(!reg.test(number)) {
nextBtn.disabled = true;
nextBtn.innerHTML = "請輸入正確的購買數量";
return;
}
// 如果當前選擇的數量大于當前的庫存的數量的話,顯示庫存不足
if(number > stock) {
nextBtn.disabled = true;
nextBtn.innerHTML = "庫存不足";
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = "放入購物車";
}
~~~
上面的代碼雖然是完成了頁面上的需求,但是我們的代碼都耦合在一起了,目前雖然問題不是很多,假如隨著以后需求的改變,SKU屬性越來越多的話,比如頁面增加一個或者多個下拉框的時候,代表選擇手機內存,現在我們需要計算顏色,內存和購買數量,來判斷nextBtn是顯示庫存不足還是放入購物車;代碼如下:
HTML代碼如下:
選擇顏色:
~~~
<select id="colorSelect">
<option value="">請選擇</option>
<option value="red">紅色</option>
<option value="blue">藍色</option>
</select>
<br/>
<br/>
~~~
選擇內存:
~~~
<select id="memorySelect">
<option value="">請選擇</option>
<option value="32G">32G</option>
<option value="64G">64G</option>
</select>
<p>輸入購買的數量: <input type="text" id="numberInput"/></p>
你選擇了的顏色:<div id="colorInfo"></div>
你選擇了內存:<div id="memoryInfo"></div>
<p>你輸入的數量: <div id="numberInfo"></div> </p>
<button id="nextBtn" disabled="true">請選擇手機顏色和購買數量</button>
~~~
JS代碼變為如下:
~~~
// 假設我們提前從后臺獲取到所有顏色手機的庫存量
var goods = {
// 手機庫存
"red|32G": 6,
"red|64G": 16,
"blue|32G": 8,
"blue|64G": 18
};
/*
我們下面分別來監聽colorSelect的下拉框的onchange事件和numberInput輸入框的oninput的事件,
然后在這兩個事件中作出相應的處理
*/
var colorSelect = document.getElementById("colorSelect"),
memorySelect = document.getElementById("memorySelect"),
numberInput = document.getElementById("numberInput"),
colorInfo = document.getElementById("colorInfo"),
numberInfo = document.getElementById("numberInfo"),
memoryInfo = document.getElementById("memoryInfo"),
nextBtn = document.getElementById("nextBtn");
// 監聽change事件
colorSelect.onchange = function(){
select();
};
numberInput.oninput = function(){
select();
};
memorySelect.onchange = function(){
select();
};
function select(){
var color = colorSelect.value, // 顏色
number = numberInput.value, // 數量
memory = memorySelect.value, // 內存
stock = goods[color + '|' +memory]; // 該顏色手機對應的當前庫存
colorInfo.innerHTML = color;
numberInfo.innerHTML = number;
memoryInfo.innerHTML = memory;
// 如果用戶沒有選擇顏色的話,禁用按鈕
if(!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = "請選擇手機顏色";
return;
}
// 判斷用戶輸入的購買數量是否是正整數
var reg = /^\d+$/g;
if(!reg.test(number)) {
nextBtn.disabled = true;
nextBtn.innerHTML = "請輸入正確的購買數量";
return;
}
// 如果當前選擇的數量大于當前的庫存的數量的話,顯示庫存不足
if(number > stock) {
nextBtn.disabled = true;
nextBtn.innerHTML = "庫存不足";
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = "放入購物車";
}
}
~~~
一般的代碼就是這樣的,感覺使用中介者模式代碼也類似,這里就不多介紹了,書上的代碼說有優點,但是個人感覺沒有什么很大的區別,因此這里就不再使用中介者模式來編寫代碼了。