javascript設(shè)計(jì)模式之中介者模式學(xué)習(xí)筆記
先來(lái)理解這么一個(gè)問(wèn)題,假如我們前端開(kāi)發(fā)接的需求是需求方給我們需求,可能一個(gè)前端開(kāi)發(fā)會(huì)和多個(gè)需求方打交道,所以會(huì)保持多個(gè)需求方的聯(lián)系,那么在程序里面就意味著保持多個(gè)對(duì)象的引用,當(dāng)程序的規(guī)模越大,對(duì)象會(huì)越來(lái)越多,他們之間的關(guān)系會(huì)越來(lái)越復(fù)雜,那現(xiàn)在假如現(xiàn)在有一個(gè)中介者(假如就是我們的主管)來(lái)對(duì)接多個(gè)需求方的需求,那么需求方只需要把所有的需求給我們主管就可以,主管會(huì)依次看我們的工作量來(lái)給我們分配任務(wù),這樣的話,我們前端開(kāi)發(fā)就不需要和多個(gè)業(yè)務(wù)方聯(lián)系,我們只需要和我們主管(也就是中介)聯(lián)系即可,這樣的好處就弱化了對(duì)象之間的耦合。
日常生活中的列子:
中介者模式對(duì)于我們?nèi)粘I钪薪?jīng)常會(huì)碰到,比如我們?nèi)シ课葜薪槿プ夥浚课葜薪槿嗽谧夥空吆头繓|出租者之間形成一條中介;租房者并不關(guān)心租誰(shuí)的房,房東出租者也并不關(guān)心它租給誰(shuí),因?yàn)橛兄薪?,所以需要中介?lái)完成這場(chǎng)交易。
中介者模式的作用是解除對(duì)象與對(duì)象之間的耦合關(guān)系,增加一個(gè)中介對(duì)象后,所有的相關(guān)對(duì)象都通過(guò)中介者對(duì)象來(lái)通信,而不是相互引用,所以當(dāng)一個(gè)對(duì)象發(fā)送改變時(shí),只需要通知中介者對(duì)象即可。中介者使各個(gè)對(duì)象之間耦合松散,而且可以獨(dú)立地改變它們之間的交互。
實(shí)現(xiàn)中介者的列子如下:
不知道大家有沒(méi)有玩過(guò)英雄殺這個(gè)游戲,最早的時(shí)候,英雄殺有2個(gè)人(分別是敵人和自己);我們針對(duì)這個(gè)游戲先使用普通的函數(shù)來(lái)實(shí)現(xiàn)如下:
比如先定義一個(gè)函數(shù),該函數(shù)有三個(gè)方法,分別是win(贏), lose(輸),和die(敵人死亡)這三個(gè)函數(shù);只要一個(gè)玩家死亡該游戲就結(jié)束了,同時(shí)需要通知它的對(duì)手勝利了; 代碼需要編寫如下:
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個(gè)對(duì)象
var h1 = new Hero("朱元璋");
var h2 = new Hero("劉伯溫");
// 給玩家設(shè)置敵人
h1.enemy = h2;
h2.enemy = h1;
// 朱元璋死了 也就輸了
h1.die(); // 輸出 朱元璋l(fā)ose 劉伯溫Won
現(xiàn)在我們?cè)賮?lái)為游戲添加隊(duì)友
比如現(xiàn)在我們來(lái)為游戲添加隊(duì)友,比如英雄殺有6人一組,那么這種情況下就有隊(duì)友,敵人也有3個(gè);因此我們需要區(qū)分是敵人還是隊(duì)友需要隊(duì)的顏色這個(gè)字段,如果隊(duì)的顏色相同的話,那么就是同一個(gè)隊(duì)的,否則的話就是敵人;
我們可以先定義一個(gè)數(shù)組players來(lái)保存所有的玩家,在創(chuàng)建玩家之后,循環(huán)players來(lái)給每個(gè)玩家設(shè)置隊(duì)友或者敵人;
var players = [];
接著我們?cè)賮?lái)編寫Hero這個(gè)函數(shù);代碼如下:
var players = []; // 定義一個(gè)數(shù)組 保存所有的玩家
function Hero(name,teamColor) {
this.friends = []; //保存隊(duì)友列表
this.enemies = []; // 保存敵人列表
this.state = 'live'; // 玩家狀態(tài)
this.name = name; // 角色名字
this.teamColor = teamColor; // 隊(duì)伍的顏色
}
Hero.prototype.win = function(){
// 贏了
console.log("win:" + this.name);
};
Hero.prototype.lose = function(){
// 輸了
console.log("lose:" + this.name);
};
Hero.prototype.die = function(){
// 所有隊(duì)友死亡情況 默認(rèn)都是活著的
var all_dead = true;
this.state = 'dead'; // 設(shè)置玩家狀態(tài)為死亡
for(var i = 0,ilen = this.friends.length; i < ilen; i+=1) {
// 遍歷,如果還有一個(gè)隊(duì)友沒(méi)有死亡的話,則游戲還未結(jié)束
if(this.friends[i].state !== 'dead') {
all_dead = false;
break;
}
}
if(all_dead) {
this.lose(); // 隊(duì)友全部死亡,游戲結(jié)束
// 循環(huán) 通知所有的玩家 游戲失敗
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();
}
}
}
// 定義一個(gè)工廠類來(lái)創(chuàng)建玩家
var heroFactory = function(name,teamColor) {
var newPlayer = new Hero(name,teamColor);
for(var i = 0,ilen = players.length; i < ilen; i+=1) {
// 如果是同一隊(duì)的玩家
if(players[i].teamColor === newPlayer.teamColor) {
// 相互添加隊(duì)友列表
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;
};
// 紅隊(duì)
var p1 = heroFactory("aa",'red'),
p2 = heroFactory("bb",'red'),
p3 = heroFactory("cc",'red'),
p4 = heroFactory("dd",'red');
// 藍(lán)隊(duì)
var p5 = heroFactory("ee",'blue'),
p6 = heroFactory("ff",'blue'),
p7 = heroFactory("gg",'blue'),
p8 = heroFactory("hh",'blue');
// 讓紅隊(duì)玩家全部死亡
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函數(shù)有2個(gè)參數(shù),分別是name(玩家名字)和teamColor(隊(duì)顏色),
首先我們可以根據(jù)隊(duì)顏色來(lái)判斷是隊(duì)友還是敵人;同樣也有三個(gè)方法win(贏),lose(輸),和die(死亡);如果每次死亡一個(gè)人的時(shí)候,循環(huán)下該死亡的隊(duì)友有沒(méi)有全部死亡,如果全部死亡了的話,就輸了,因此需要循環(huán)他們的隊(duì)友,分別告訴每個(gè)隊(duì)友中的成員他們輸了,同時(shí)需要循環(huán)他們的敵人,分別告訴他們的敵人他們贏了;因此每次死了一個(gè)人的時(shí)候,都需要循環(huán)一次判斷他的隊(duì)友是否都死亡了;因此每個(gè)玩家和其他的玩家都是緊緊耦合在一起了。
下面我們可以使用中介者模式來(lái)改善上面的demo;
首先我們?nèi)匀欢xHero構(gòu)造函數(shù)和Hero對(duì)象原型的方法,在Hero對(duì)象的這些原型方法中,不再負(fù)責(zé)具體的執(zhí)行的邏輯,而是把操作轉(zhuǎn)交給中介者對(duì)象,中介者對(duì)象來(lái)負(fù)責(zé)做具體的事情,我們可以把中介者對(duì)象命名為playerDirector;
在playerDirector開(kāi)放一個(gè)對(duì)外暴露的接口ReceiveMessage,負(fù)責(zé)接收player對(duì)象發(fā)送的消息,而player對(duì)象發(fā)送消息的時(shí)候,總是把自身的this作為參數(shù)發(fā)送給playerDirector,以便playerDirector 識(shí)別消息來(lái)自于那個(gè)玩家對(duì)象。
代碼如下:
var players = []; // 定義一個(gè)數(shù)組 保存所有的玩家
function Hero(name,teamColor) {
this.state = 'live'; // 玩家狀態(tài)
this.name = name; // 角色名字
this.teamColor = teamColor; // 隊(duì)伍的顏色
}
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';
// 給中介者發(fā)送消息,玩家死亡
playerDirector.ReceiveMessage('playerDead',this);
}
// 移除玩家
Hero.prototype.remove = function(){
// 給中介者發(fā)送一個(gè)消息,移除一個(gè)玩家
playerDirector.ReceiveMessage('removePlayer',this);
};
// 玩家換隊(duì)
Hero.prototype.changeTeam = function(color) {
// 給中介者發(fā)送一個(gè)消息,玩家換隊(duì)
playerDirector.ReceiveMessage('changeTeam',this,color);
};
// 定義一個(gè)工廠類來(lái)創(chuàng)建玩家
var heroFactory = function(name,teamColor) {
// 創(chuàng)建一個(gè)新的玩家對(duì)象
var newHero = new Hero(name,teamColor);
// 給中介者發(fā)送消息,新增玩家
playerDirector.ReceiveMessage('addPlayer',newHero);
return newHero;
};
var playerDirector = (function(){
var players = {}, // 保存所有的玩家
operations = {}; // 中介者可以執(zhí)行的操作
// 新增一個(gè)玩家操作
operations.addPlayer = function(player) {
// 獲取玩家隊(duì)友的顏色
var teamColor = player.teamColor;
// 如果該顏色的玩家還沒(méi)有隊(duì)伍的話,則新成立一個(gè)隊(duì)伍
players[teamColor] = players[teamColor] || [];
// 添加玩家進(jìn)隊(duì)伍
players[teamColor].push(player);
};
// 移除一個(gè)玩家
operations.removePlayer = function(player){
// 獲取隊(duì)伍的顏色
var teamColor = player.teamColor,
// 獲取該隊(duì)伍的所有成員
teamPlayers = players[teamColor] || [];
// 遍歷
for(var i = teamPlayers.length - 1; i>=0; i--) {
if(teamPlayers[i] === player) {
teamPlayers.splice(i,1);
}
}
};
// 玩家換隊(duì)
operations.changeTeam = function(player,newTeamColor){
// 首先從原隊(duì)伍中刪除
operations.removePlayer(player);
// 然后改變隊(duì)伍的顏色
player.teamColor = newTeamColor;
// 增加到隊(duì)伍中
operations.addPlayer(player);
};
// 玩家死亡
operations.playerDead = function(player) {
var teamColor = player.teamColor,
// 玩家所在的隊(duì)伍
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的話 說(shuō)明全部死亡
if(all_dead) {
for(var i = 0, player; player = teamPlayers[i++]; ) {
// 本隊(duì)所有玩家lose
player.lose();
}
for(var color in players) {
if(color !== teamColor) {
// 說(shuō)明這是另外一組隊(duì)伍
// 獲取該隊(duì)伍的玩家
var teamPlayers = players[color];
for(var i = 0,player; player = teamPlayers[i++]; ) {
player.win(); // 遍歷通知其他玩家win了
}
}
}
}
};
var ReceiveMessage = function(){
// arguments的第一個(gè)參數(shù)為消息名稱 獲取第一個(gè)參數(shù)
var message = Array.prototype.shift.call(arguments);
operations[message].apply(this,arguments);
};
return {
ReceiveMessage : ReceiveMessage
};
})();
// 紅隊(duì)
var p1 = heroFactory("aa",'red'),
p2 = heroFactory("bb",'red'),
p3 = heroFactory("cc",'red'),
p4 = heroFactory("dd",'red');
// 藍(lán)隊(duì)
var p5 = heroFactory("ee",'blue'),
p6 = heroFactory("ff",'blue'),
p7 = heroFactory("gg",'blue'),
p8 = heroFactory("hh",'blue');
// 讓紅隊(duì)玩家全部死亡
p1.die();
p2.die();
p3.die();
p4.die();
// lose:aa lose:bb lose:cc lose:dd
// win:ee win:ff win:gg win:hh
我們可以看到如上代碼;玩家與玩家之間的耦合代碼已經(jīng)解除了,而把所有的邏輯操作放在中介者對(duì)象里面進(jìn)去處理,某個(gè)玩家的任何操作不需要去遍歷去通知其他玩家,而只是需要給中介者發(fā)送一個(gè)消息即可,中介者接受到該消息后進(jìn)行處理,處理完消息之后會(huì)把處理結(jié)果反饋給其他的玩家對(duì)象。使用中介者模式解除了對(duì)象與對(duì)象之間的耦合代碼; 使程序更加的靈活.
中介者模式實(shí)現(xiàn)購(gòu)買商品的列子
下面的列子是書上的列子,比如在淘寶或者天貓的列子不是這樣實(shí)現(xiàn)的,也沒(méi)有關(guān)系,我們可以改動(dòng)下即可,我們最主要來(lái)學(xué)習(xí)下使用中介者模式來(lái)實(shí)現(xiàn)的思路。
首先先介紹一下業(yè)務(wù):在購(gòu)買流程中,可以選擇手機(jī)的顏色以及輸入購(gòu)買的數(shù)量,同時(shí)頁(yè)面中有2個(gè)展示區(qū)域,分別顯示用戶剛剛選擇好的顏色和數(shù)量。還有一個(gè)按鈕動(dòng)態(tài)顯示下一步的操作,我們需要查詢?cè)擃伾謾C(jī)對(duì)應(yīng)的庫(kù)存,如果庫(kù)存數(shù)量小于這次的購(gòu)買數(shù)量,按鈕則被禁用并且顯示庫(kù)存不足的文案,反之按鈕高亮且可以點(diǎn)擊并且顯示假如購(gòu)物車。
HTML代碼如下:
選擇顏色:
<select id="colorSelect">
<option value="">請(qǐng)選擇</option>
<option value="red">紅色</option>
<option value="blue">藍(lán)色</option>
</select>
<p>輸入購(gòu)買的數(shù)量: <input type="text" id="numberInput"/></p>
你選擇了的顏色:<div id="colorInfo"></div>
<p>你輸入的數(shù)量: <div id="numberInfo"></div> </p>
<button id="nextBtn" disabled="true">請(qǐng)選擇手機(jī)顏色和購(gòu)買數(shù)量</button>
首先頁(yè)面上有一個(gè)select選擇框,然后有輸入的購(gòu)買數(shù)量輸入框,還有2個(gè)展示區(qū)域,分別是選擇的顏色和輸入的數(shù)量的顯示的區(qū)域,還有下一步的按鈕操作;
我們先定義一下:
假設(shè)我們提前從后臺(tái)獲取到所有顏色手機(jī)的庫(kù)存量
var goods = {
// 手機(jī)庫(kù)存
"red": 6,
"blue": 8
};
接著 我們下面分別來(lái)監(jiān)聽(tīng)colorSelect的下拉框的onchange事件和numberInput輸入框的oninput的事件,然后在這兩個(gè)事件中作出相應(yīng)的處理
常規(guī)的JS代碼如下:
// 假設(shè)我們提前從后臺(tái)獲取到所有顏色手機(jī)的庫(kù)存量
var goods = {
// 手機(jī)庫(kù)存
"red": 6,
"blue": 8
};
/*
我們下面分別來(lái)監(jiān)聽(tīng)colorSelect的下拉框的onchange事件和numberInput輸入框的oninput的事件,
然后在這兩個(gè)事件中作出相應(yīng)的處理
*/
var colorSelect = document.getElementById("colorSelect"),
numberInput = document.getElementById("numberInput"),
colorInfo = document.getElementById("colorInfo"),
numberInfo = document.getElementById("numberInfo"),
nextBtn = document.getElementById("nextBtn");
// 監(jiān)聽(tīng)change事件
colorSelect.onchange = function(e){
select();
};
numberInput.oninput = function(){
select();
};
function select(){
var color = colorSelect.value, // 顏色
number = numberInput.value, // 數(shù)量
stock = goods[color]; // 該顏色手機(jī)對(duì)應(yīng)的當(dāng)前庫(kù)存
colorInfo.innerHTML = color;
numberInfo.innerHTML = number;
// 如果用戶沒(méi)有選擇顏色的話,禁用按鈕
if(!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = "請(qǐng)選擇手機(jī)顏色";
return;
}
// 判斷用戶輸入的購(gòu)買數(shù)量是否是正整數(shù)
var reg = /^\d+$/g;
if(!reg.test(number)) {
nextBtn.disabled = true;
nextBtn.innerHTML = "請(qǐng)輸入正確的購(gòu)買數(shù)量";
return;
}
// 如果當(dāng)前選擇的數(shù)量大于當(dāng)前的庫(kù)存的數(shù)量的話,顯示庫(kù)存不足
if(number > stock) {
nextBtn.disabled = true;
nextBtn.innerHTML = "庫(kù)存不足";
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = "放入購(gòu)物車";
}
上面的代碼雖然是完成了頁(yè)面上的需求,但是我們的代碼都耦合在一起了,目前雖然問(wèn)題不是很多,假如隨著以后需求的改變,SKU屬性越來(lái)越多的話,比如頁(yè)面增加一個(gè)或者多個(gè)下拉框的時(shí)候,代表選擇手機(jī)內(nèi)存,現(xiàn)在我們需要計(jì)算顏色,內(nèi)存和購(gòu)買數(shù)量,來(lái)判斷nextBtn是顯示庫(kù)存不足還是放入購(gòu)物車;代碼如下:
HTML代碼如下:
選擇顏色:
<select id="colorSelect">
<option value="">請(qǐng)選擇</option>
<option value="red">紅色</option>
<option value="blue">藍(lán)色</option>
</select>
<br/>
<br/>
選擇內(nèi)存:
<select id="memorySelect">
<option value="">請(qǐng)選擇</option>
<option value="32G">32G</option>
<option value="64G">64G</option>
</select>
<p>輸入購(gòu)買的數(shù)量: <input type="text" id="numberInput"/></p>
你選擇了的顏色:<div id="colorInfo"></div>
你選擇了內(nèi)存:<div id="memoryInfo"></div>
<p>你輸入的數(shù)量: <div id="numberInfo"></div> </p>
<button id="nextBtn" disabled="true">請(qǐng)選擇手機(jī)顏色和購(gòu)買數(shù)量</button>
JS代碼變?yōu)槿缦拢?/p>
// 假設(shè)我們提前從后臺(tái)獲取到所有顏色手機(jī)的庫(kù)存量
var goods = {
// 手機(jī)庫(kù)存
"red|32G": 6,
"red|64G": 16,
"blue|32G": 8,
"blue|64G": 18
};
/*
我們下面分別來(lái)監(jiān)聽(tīng)colorSelect的下拉框的onchange事件和numberInput輸入框的oninput的事件,
然后在這兩個(gè)事件中作出相應(yīng)的處理
*/
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");
// 監(jiān)聽(tīng)change事件
colorSelect.onchange = function(){
select();
};
numberInput.oninput = function(){
select();
};
memorySelect.onchange = function(){
select();
};
function select(){
var color = colorSelect.value, // 顏色
number = numberInput.value, // 數(shù)量
memory = memorySelect.value, // 內(nèi)存
stock = goods[color + '|' +memory]; // 該顏色手機(jī)對(duì)應(yīng)的當(dāng)前庫(kù)存
colorInfo.innerHTML = color;
numberInfo.innerHTML = number;
memoryInfo.innerHTML = memory;
// 如果用戶沒(méi)有選擇顏色的話,禁用按鈕
if(!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = "請(qǐng)選擇手機(jī)顏色";
return;
}
// 判斷用戶輸入的購(gòu)買數(shù)量是否是正整數(shù)
var reg = /^\d+$/g;
if(!reg.test(number)) {
nextBtn.disabled = true;
nextBtn.innerHTML = "請(qǐng)輸入正確的購(gòu)買數(shù)量";
return;
}
// 如果當(dāng)前選擇的數(shù)量大于當(dāng)前的庫(kù)存的數(shù)量的話,顯示庫(kù)存不足
if(number > stock) {
nextBtn.disabled = true;
nextBtn.innerHTML = "庫(kù)存不足";
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = "放入購(gòu)物車";
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
KnockoutJS 3.X API 第四章之?dāng)?shù)據(jù)控制流component綁定
這篇文章主要介紹了KnockoutJS 3.X API 第四章之?dāng)?shù)據(jù)控制流component綁定的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10
JavaScript實(shí)現(xiàn)手寫promise的示例代碼
promise?作為前端開(kāi)發(fā)中常用的函數(shù),解決了?js?處理異步時(shí)回調(diào)地獄的問(wèn)題,大家應(yīng)該也不陌生了,今天來(lái)學(xué)習(xí)一下?promise?的實(shí)現(xiàn)過(guò)程吧2023-04-04
微信小程序?qū)崿F(xiàn)圖片上傳、刪除和預(yù)覽功能的方法
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)圖片上傳、刪除和預(yù)覽功能的方法,涉及微信小程序界面布局、事件響應(yīng)及圖片操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-12-12
javascript sudoku 數(shù)獨(dú)智力游戲生成代碼
javascript sudoku 數(shù)獨(dú)智力游戲生成代碼,喜歡的朋友可以參考下。2010-03-03
js window.onload 加載多個(gè)函數(shù)和追加函數(shù)詳解
本篇文章主要是對(duì)js window.onload 加載多個(gè)函數(shù)和追加函數(shù)進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01
值得分享和收藏的Bootstrap學(xué)習(xí)教程
這絕對(duì)是一套值得分享和大家收藏的Bootstrap學(xué)習(xí)教程,完整的知識(shí)體系,系統(tǒng)的學(xué)習(xí)資料,幫助大家開(kāi)啟Bootstrap學(xué)習(xí)之旅,享受Bootstrap帶給大家的奇妙樂(lè)趣2016-05-05
微信小程序?qū)崿F(xiàn)倒計(jì)時(shí)功能
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)倒計(jì)時(shí)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11

