????策略模式的定義是:定義一系列的算法,把它們一個(gè)個(gè)封裝起來(lái),并且使它們可以相互替換。
????俗話說(shuō),條條大路通羅馬。在美劇《越獄》中,主角 Michael Scofield 就設(shè)計(jì)了兩條越獄的道路。這兩條道路都可以到達(dá)靠近監(jiān)獄外墻的醫(yī)務(wù)室。
????同樣,在現(xiàn)實(shí)中,很多時(shí)候也有多種途徑到達(dá)同一個(gè)目的地。比如我們要去某個(gè)地方旅游,可以根據(jù)具體的實(shí)際情況來(lái)選擇出行的線路。
????? 如果沒(méi)有時(shí)間但是不在乎錢,可以選擇坐飛機(jī)。
????? 如果沒(méi)有錢,可以選擇坐大巴或者火車。
????? 如果再窮一點(diǎn),可以選擇騎自行車。

????在程序設(shè)計(jì)中,我們也常常遇到類似的情況,要實(shí)現(xiàn)某一個(gè)功能有多種方案可以選擇。比如一個(gè)壓縮文件的程序,既可以選擇 zip 算法,也可以選擇 gzip 算法。
????這些算法靈活多樣,而且可以隨意互相替換。這種解決方案就是策略模式。
使用策略模式計(jì)算獎(jiǎng)金
????策略模式有著廣泛的應(yīng)用。我們以年終獎(jiǎng)的計(jì)算為例進(jìn)行介紹。
? ??很多公司的年終獎(jiǎng)是根據(jù)員工的工資基數(shù)和年底績(jī)效情況來(lái)發(fā)放的。例如,績(jī)效為 S 的人年終獎(jiǎng)有 4 倍工資,績(jī)效為 A 的人年終獎(jiǎng)有 3 倍工資,而績(jī)效為 B 的人年終獎(jiǎng)是 2 倍工資。假設(shè)財(cái)務(wù)部要求我們提供一段代碼,來(lái)方便他們計(jì)算員工的年終獎(jiǎng)。
1. 最初的代碼實(shí)現(xiàn)
????我們可以編寫一個(gè)名為 calculateBonus 的函數(shù)來(lái)計(jì)算每個(gè)人的獎(jiǎng)金數(shù)額。很顯然,calculateBonus 函數(shù)要正確工作,就需要接收兩個(gè)參數(shù):?jiǎn)T工的工資數(shù)額和他的績(jī)效考核等級(jí)。
代碼如下:
var calculateBonus = function( performanceLevel, salary ){
????if ( performanceLevel === 'S' ){
????????return salary * 4;
????}
????if ( performanceLevel === 'A' ){
????????return salary * 3;
????}
????if ( performanceLevel === 'B' ){
????????return salary * 2;
????}
};
calculateBonus( 'B', 20000 ); // 輸出:40000
calculateBonus( 'S', 6000 ); // 輸出:24000
可以發(fā)現(xiàn),這段代碼十分簡(jiǎn)單,但是存在著顯而易見(jiàn)的缺點(diǎn)。
????? calculateBonus 函數(shù)比較龐大,包含了很多 if-else 語(yǔ)句,這些語(yǔ)句需要覆蓋所有的邏輯分支。
????? calculateBonus 函數(shù)缺乏彈性,如果增加了一種新的績(jī)效等級(jí) C,或者想把績(jī)效 S 的獎(jiǎng)金系數(shù)改為 5,那我們必須深入 calculateBonus 函數(shù)的內(nèi)部實(shí)現(xiàn),這是違反開(kāi)放?封閉原則的。
????? 算法的復(fù)用性差,如果在程序的其他地方需要重用這些計(jì)算獎(jiǎng)金的算法呢?我們的選擇只有復(fù)制和粘貼。
使用策略模式重構(gòu)代碼
????經(jīng)過(guò)思考,我們想到了更好的辦法——使用策略模式來(lái)重構(gòu)代碼。策略模式指的是定義一系列的算法,把它們一個(gè)個(gè)封裝起來(lái)。將不變的部分和變化的部分隔開(kāi)是每個(gè)設(shè)計(jì)模式的主題,策略模式也不例外,策略模式的目的就是將算法的使用與算法的實(shí)現(xiàn)分離開(kāi)來(lái)。
????在這個(gè)例子里,算法的使用方式是不變的,都是根據(jù)某個(gè)算法取得計(jì)算后的獎(jiǎng)金數(shù)額。而算法的實(shí)現(xiàn)是各異和變化的,每種績(jī)效對(duì)應(yīng)著不同的計(jì)算規(guī)則。
????一個(gè)基于策略模式的程序至少由兩部分組成。第一個(gè)部分是一組策略類,策略類封裝了具體的算法,并負(fù)責(zé)具體的計(jì)算過(guò)程。第二個(gè)部分是環(huán)境類 Context,Context 接受客戶的請(qǐng)求,隨后把請(qǐng)求委托給某一個(gè)策略類。要做到這點(diǎn),說(shuō)明 Context 中要維持對(duì)某個(gè)策略對(duì)象的引用。
????現(xiàn)在用策略模式來(lái)重構(gòu)上面的代碼。
// 策略對(duì)象
var strategies = {
????"S": function( salary ){
????????return salary * 4;
????},
????"A": function( salary ){
????????return salary * 3;
????},
????"B": function( salary ){
????????return salary * 2;
????}
};
var calculateBonus = function( level, salary ){
????return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 輸出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 輸出:30000
策略模式的優(yōu)缺點(diǎn)
????策略模式是一種常用且有效的設(shè)計(jì)模式,策略模式的一些優(yōu)點(diǎn)。
????? 策略模式利用組合、委托和多態(tài)等技術(shù)和思想,可以有效地避免多重條件選擇語(yǔ)句。
????? 策略模式提供了對(duì)開(kāi)放—封閉原則的完美支持,將算法封裝在獨(dú)立的 strategy 中,使得它們易于切換,易于理解,易于擴(kuò)展。
????? 策略模式中的算法也可以復(fù)用在系統(tǒng)的其他地方,從而避免許多重復(fù)的復(fù)制粘貼工作。
????? 在策略模式中利用組合和委托來(lái)讓 Context 擁有執(zhí)行算法的能力,這也是繼承的一種更輕便的替代方案。
????當(dāng)然,策略模式也有一些缺點(diǎn),但這些缺點(diǎn)并不嚴(yán)重。
????首先,使用策略模式會(huì)在程序中增加許多策略類或者策略對(duì)象,但實(shí)際上這比把它們負(fù)責(zé)的邏輯堆砌在 Context 中要好。
????其次,要使用策略模式,必須了解所有的 strategy,必須了解各個(gè) strategy 之間的不同點(diǎn),這樣才能選擇一個(gè)合適的 strategy。比如,我們要選擇一種合適的旅游出行路線,必須先了解選擇飛機(jī)、火車、自行車等方案的細(xì)節(jié)。此時(shí) strategy 要向客戶暴露它的所有實(shí)現(xiàn),這是違反最少知識(shí)原則的。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ——摘自《JavaScript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐 》