何爲策略?好比咱們要去某個地方旅遊,能夠根據具體的實際狀況來選擇出行的線路。javascript
在程序設計中,咱們也經常遇到相似的狀況,要實現某一個功能有多種方案能夠選擇。好比一個壓縮文件的程序,既能夠選擇zip算法,也能夠選擇gzip算法。css
定義:策略模式定義一系列的算法,分別封裝起來,讓他們之間能夠互相替換,此模式讓算法的變化獨立於使用算飯的客戶.html
策略模式有着普遍的應用。本節咱們就以年終獎的計算爲例進行介紹。java
不少公司的年終獎是根據員工的工資基數和年末績效狀況來發放的。例如,績效爲S的人年終獎有4倍工資,績效爲A的人年終獎有3倍工資,而績效爲B的人年終獎是2倍工資。假設財務部要求咱們提供一段代碼,來方便他們計算員工的年終獎。jquery
1. 最初的代碼實現算法
咱們能夠編寫一個名爲calculateBonus的函數來計算每一個人的獎金數額。很顯然,calculateBonus函數要正確工做,就須要接收兩個參數:員工的工資數額和他的績效考覈等級。代碼以下:設計模式
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
能夠發現,這段代碼十分簡單,可是存在着顯而易見的缺點。ruby
calculateBonus
函數比較龐大,包含了不少if-else
語句,這些語句須要覆蓋全部的邏輯分支。markdown
calculateBonus
函數缺少彈性,若是增長了一種新的績效等級C,或者想把績效S的獎金係數改成5,那咱們必須深刻calculateBonus
函數的內部實現,這是違反開放-封閉原則的。函數
算法的複用性差,若是在程序的其餘地方須要重用這些計算獎金的算法呢?咱們的選擇只有複製和粘貼。所以,咱們須要重構這段代碼。
2. 使用組合函數重構代碼
通常最容易想到的辦法就是使用組合函數來重構它,咱們把各類算法封裝到一個個的小函數裏面,這些小函數有着良好的命名,能夠一目瞭然地知道它對應着哪一種算法,它們也能夠被複用在程序的其餘地方。代碼以下:
var performanceS = function( salary ){
return salary * 4;
};
var performanceA = function( salary ){
return salary * 3;
};
var performanceB = function( salary ){
return salary * 2;
};
var calculateBonus = function( performanceLevel, salary ){
if ( performanceLevel === 'S' ){
return performanceS( salary );
}
if ( performanceLevel === 'A' ){
return performanceA( salary );
}
if ( performanceLevel === 'B' ){
return performanceB( salary );
}
};
calculateBonus( 'A' , 10000 ); // 輸出:30000
目前,咱們的程序獲得了必定的改善,但這種改善很是有限,咱們依然沒有解決最重要的問題:calculateBonus函數有可能愈來愈龐大,並且在系統變化的時候缺少彈性。
3. 使用策略模式重構代碼
通過思考,咱們想到了更好的辦法——使用策略模式來重構代碼。策略模式指的是定義一系列的算法,把它們一個個封裝起來。將不變的部分和變化的部分隔開是每一個設計模式的主題,策略模式也不例外,策略模式的目的就是將算法的使用與算法的實現分離開來。
在這個例子裏,算法的使用方式是不變的,都是根據某個算法取得計算後的獎金數額。而算法的實現是各異和變化的,每種績效對應着不一樣的計算規則。
一個基於策略模式的程序至少由兩部分組成。第一個部分是一組策略類,策略類封裝了具體的算法,並負責具體的計算過程。 第二個部分是環境類Context,Context接受客戶的請求,隨後把請求委託給某一個策略類。要作到這點,說明Context中要維持對某個策略對象的引用。
如今用策略模式來重構上面的代碼。第一個版本是模仿傳統面嚮對象語言中的實現。咱們先把每種績效的計算規則都封裝在對應的策略類裏面:
var performanceS = function(){};
performanceS.prototype.calculate = function( salary ){
return salary * 4;
};
var performanceA = function(){};
performanceA.prototype.calculate = function( salary ){
return salary * 3;
};
var performanceB = function(){};
performanceB.prototype.calculate = function( salary ){
return salary * 2;
};
接下來定義獎金類Bonus:
var Bonus = function(){
this.salary = null; //原始工資
this.strategy = null; //績效等級對應的策略對象
};
Bonus.prototype.setSalary = function( salary ){
this.salary = salary; //設置員工的原始工資
};
Bonus.prototype.setStrategy = function( strategy ){
this.strategy = strategy; //設置員工績效等級對應的策略對象
};
Bonus.prototype.getBonus = function(){ //取得獎金數額
return this.strategy.calculate( this.salary ); //把計算獎金的操做委託給對應的策略對象
};
在完成最終的代碼以前,咱們再來回顧一下策略模式的思想:
定義一系列的算法,把它們一個個封裝起來,而且使它們能夠相互替換。
這句話若是說得更詳細一點,就是:定義一系列的算法,把它們各自封裝成策略類,算法被封裝在策略類內部的方法裏。在客戶對Context發起請求的時候,Context老是把請求委託給這些策略對象中間的某一個進行計算。
「而且使它們能夠相互替換」,這句話在很大程度上是相對於靜態類型語言而言的。由於靜態類型語言中有類型檢查機制,因此各個策略類須要實現一樣的接口。當它們的真正類型被隱藏在接口後面時,它們才能被相互替換。而在JavaScript這種「類型模糊」的語言中沒有這種困擾,任何對象均可以被替換使用。所以,JavaScript中的「能夠相互替換使用」表現爲它們具備相同的目標和意圖。
如今咱們來完成這個例子中剩下的代碼。先建立一個bonus對象,而且給bonus對象設置一些原始的數據,好比員工的原始工資數額。接下來把某個計算獎金的策略對象也傳入bonus對象內部保存起來。當調用bonus.getBonus()來計算獎金的時候,bonus對象自己並無能力進行計算,而是把請求委託給了以前保存好的策略對象:
var bonus = new Bonus();
bonus.setSalary( 10000 );
bonus.setStrategy( new performanceS() ); //設置策略對象
console.log( bonus.getBonus() ); // 輸出:40000
bonus.setStrategy( new performanceA() ); //設置策略對象
console.log( bonus.getBonus() ); // 輸出:30000
剛剛咱們用策略模式重構了這段計算年終獎的代碼,能夠看到經過策略模式重構以後,代碼變得更加清晰,各個類的職責更加鮮明。但這段代碼是基於傳統面嚮對象語言的模仿,下一節咱們將瞭解用JavaScript實現的策略模式。
在5.1節中,咱們讓strategy對象從各個策略類中建立而來,這是模擬一些傳統面嚮對象語言的實現。實際上在JavaScript語言中,函數也是對象,因此更簡單和直接的作法是把strategy直接定義爲函數:
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;
}
};
一樣,Context也沒有必要必須用Bonus類來表示,咱們依然用calculateBonus 函數充當Context來接受用戶的請求。通過改造,代碼的結構變得更加簡潔:
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
一個小例子就能讓咱們一目瞭然。
回憶下jquery裏的animate方法.
$( div ).animate( {"left: 200px"}, 1000, 'linear' );
//勻速運動
$( div ).animate( {"left: 200px"}, 1000, 'cubic' );
//三次方的緩動
這2句代碼都是讓div在1000ms內往右移動200個像素. linear(勻速)和cubic(三次方緩動)就是一種策略模式的封裝.
再來一個例子. 不少頁面都會有個即時驗證的表單. 表單的每一個成員都會有一些不一樣的驗證規則.
好比姓名框裏面, 須要驗證非空,敏感詞,字符過長這幾種狀況。 固然是能夠寫3個if else
來解決,不過這樣寫代碼的擴展性和維護性可想而知。若是表單裏面的元素多一點,須要校驗的狀況多一點,加起來寫上百個if else
也不是沒有可能。
因此更好的作法是把每種驗證規則都用策略模式單獨的封裝起來。須要哪一種驗證的時候只須要提供這個策略的名字。就像這樣:
nameInput.addValidata({ notNull: true, dirtyWords: true, maxLength: 30 })
而notNull
,maxLength
等方法只須要統一的返回true或者false,來表示是否經過了驗證。
validataList = {
notNull: function( value ){
return value !== '';
},
maxLength: function( value, maxLen ){
return value.length() > maxLen;
}
}
能夠看到,各類驗證規則很容易被修改和相互替換。若是某天產品經理建議字符過長的限制改爲60個字符。那隻須要0.5秒完成此次工做。
內容摘自:
JavaScript設計模式與開發實踐
http://www.cnblogs.com/TomXu/archive/2012/03/05/2358552.html
http://my.oschina.net/fengshuzi/blog/192107
http://www.oschina.net/translate/strategy-design-pattern-in-javascript?print