
策略模式:定義一系列的算法,把它們一個(gè)個(gè)封裝起來(lái),并且使它們可以相互替換
生活小栗子:諸葛錦囊
諸葛給劉備的錦囊妙計(jì),遇到任何困難都有應(yīng)對(duì)計(jì)策。策略模式實(shí)現(xiàn)的也是類似的場(chǎng)景。
再來(lái)一栗:給喜歡的女生買冰淇淋,事先不了解其喜好,只能集齊各種味道,總會(huì)命中。就是比較 “費(fèi)錢”,這也是策略模式的缺點(diǎn),需事先考慮所有應(yīng)對(duì)場(chǎng)景。
模式特點(diǎn)
- 策略類:算法封裝成獨(dú)立的函數(shù)/對(duì)象
- 環(huán)境類:根據(jù)不同參數(shù)調(diào)用對(duì)應(yīng)的策略函數(shù)/對(duì)象執(zhí)行
模式實(shí)現(xiàn)
實(shí)現(xiàn)方式:一個(gè)基于策略模式的程序至少由兩部分組成,第一個(gè)部分是一組策略類 Strategies(可變),策略類封裝類具體的算法,并負(fù)責(zé)具體的計(jì)算過(guò)程。第二個(gè)部分是環(huán)境類 Context(不變), Context 接收客戶的請(qǐng)求,隨后把請(qǐng)求委托給某一個(gè)策略類。
假設(shè)我們一個(gè)開(kāi)發(fā)團(tuán)隊(duì),人員組成包括(開(kāi)發(fā)組長(zhǎng),后端,前端,測(cè)試)。開(kāi)發(fā)組長(zhǎng)領(lǐng)取開(kāi)發(fā)任務(wù)(不變),但具體的任務(wù)執(zhí)行人員可根據(jù)類型劃分(可變)。
比如開(kāi)發(fā)任務(wù)有以下幾項(xiàng):
- 優(yōu)化服務(wù)器緩存(后端任務(wù))
- 優(yōu)化首屏加載速度(前端任務(wù))
- 完成系統(tǒng)并發(fā)測(cè)試(測(cè)試任務(wù))
開(kāi)發(fā)組長(zhǎng)會(huì)根據(jù)任務(wù)類型,分發(fā)到對(duì)應(yīng)的開(kāi)發(fā)人員頭上,組長(zhǎng)不承擔(dān)具體開(kāi)發(fā)任務(wù)。所以每一個(gè)開(kāi)發(fā)人員就承擔(dān) Strategy 的作用(獨(dú)立的任務(wù)執(zhí)行),而組長(zhǎng)擁有并可支配所有開(kāi)發(fā)人員的資源,充當(dāng) Context 的角色。團(tuán)隊(duì)每一個(gè)開(kāi)發(fā)人員“組合”起來(lái)就是一個(gè) Strategies 類(執(zhí)行開(kāi)發(fā)任務(wù))。 這個(gè) Strategies 是可變的,如果說(shuō)后續(xù)開(kāi)發(fā)任務(wù)需要安卓的、IOS的支持,只要添加安卓、IOS開(kāi)發(fā)人員配置即可(可擴(kuò)展)。
// 策略類(開(kāi)發(fā)人員)
var Strategies = {
"backend": function(task) {
console.log('進(jìn)行后端任務(wù):', task);
},
"frontend": function(task) {
console.log('進(jìn)行前端任務(wù):', task);
},
"testend": function(task) {
console.log('進(jìn)行測(cè)試任務(wù):', task);
}
};
// 環(huán)境類(開(kāi)發(fā)組長(zhǎng))
var Context = function(type, task) {
typeof Strategies[type] === 'function' && Strategies[type](task);
}
Context('backend', '優(yōu)化服務(wù)器緩存');
Context('frontend', '優(yōu)化首頁(yè)加載速度');
Context('testend', '完成系統(tǒng)并發(fā)測(cè)試');
上述代碼帶來(lái)的好處:
- 算法獨(dú)立封裝,任務(wù)分發(fā);
開(kāi)發(fā)組長(zhǎng)不承擔(dān)具體開(kāi)發(fā)任務(wù)(只做頂層設(shè)計(jì),不跟年輕人搶飯碗) - 復(fù)用性更好,不局限于 Context 調(diào)用;
開(kāi)發(fā)人員不愁下家(去哪寫代碼都是寫代碼)
策略模式的另一個(gè)好處就是,消除了大部分的 if...else / switch...case 條件分支語(yǔ)句,代碼閱讀性提高。
// 沒(méi)有使用策略模式的組長(zhǎng)...
var Context = function(type, task) {
if (type === 'backend') {
// 把后端給我叫來(lái)
} else if (type === 'frontend') {
// 把前端給我叫來(lái)
} else if (type === 'testend') {
// 把測(cè)試給我叫來(lái)
}
}
JavaScript 中,函數(shù)作為“一等公民“,也稱“一等對(duì)象”。JavaScript 中 ”高階函數(shù)“ 應(yīng)用中,函數(shù)可被作為變量或參數(shù)進(jìn)行傳遞或調(diào)用。因此在 JavaScript 中,我們可將算法封裝成獨(dú)立的函數(shù),并將它作為參數(shù)傳遞給另一個(gè)函數(shù)調(diào)用。
// 封裝獨(dú)立的函數(shù)
var backend = function(task) {
console.log('進(jìn)行后端任務(wù):', task);
};
var frontend = function(task) {
console.log('進(jìn)行前端任務(wù):', task);
};
var testend = function(task) {
console.log('進(jìn)行測(cè)試任務(wù):', task);
};
// 環(huán)境類(開(kāi)發(fā)組長(zhǎng))
var Context = function(func, task) {
typeof func === 'function' && func(task);
}
Context(backend, '優(yōu)化服務(wù)器緩存');
Context(frontend, '優(yōu)化首頁(yè)加載速度');
Context(testend, '完成系統(tǒng)并發(fā)測(cè)試');
少了 Strategies 策略類的外層包裹,函數(shù)更加獨(dú)立,并不妨礙其調(diào)用。使用函數(shù)替代策略類方式,正是我們?nèi)粘i_(kāi)發(fā)中經(jīng)常用到的 “隱形” 策略模式。
適用場(chǎng)景
- 多重條件語(yǔ)句判斷,執(zhí)行對(duì)應(yīng)的算法場(chǎng)景
- 表單校驗(yàn)(validator)
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):
- 利用組合、委托、多態(tài)的技術(shù)和思想,避免多重條件選擇語(yǔ)句
if...else/switch...case; - 復(fù)用性更高,算法函數(shù)可在系統(tǒng)其它地方使用;
- 支持設(shè)計(jì)模式 “開(kāi)發(fā)-封閉原則“ ,算法封裝在獨(dú)立的 Strategy 中,易于維護(hù)和擴(kuò)展;
- 策略模式使用 “組合和委托” 來(lái)讓 Context 擁有執(zhí)行算法的能力,一種替換對(duì)象繼承的可行方案
- 利用組合、委托、多態(tài)的技術(shù)和思想,避免多重條件選擇語(yǔ)句
- 缺點(diǎn):
- 增加了許多策略類或?qū)ο螅ㄩ_(kāi)發(fā)人員職能劃分明確,人員成本有所增加);
- 必須了解各個(gè) Strategy 的不同點(diǎn),違反 “最少知識(shí)原則”(組長(zhǎng)手底下有對(duì)應(yīng)的開(kāi)發(fā)人員,才不用自己那么苦逼)
參考文章
- 《JavaScript 設(shè)計(jì)模式》
- 《JavaScript 設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》
- 《JavaScript 設(shè)計(jì)模式系統(tǒng)講解與應(yīng)用》
本文首發(fā)Github,期待Star!
https://github.com/ZengLingYong/blog
作者:以樂(lè)之名
本文原創(chuàng),有不當(dāng)?shù)牡胤綒g迎指出。轉(zhuǎn)載請(qǐng)指明出處。