2 策略模式
策略模式定義: 定義一系列的算法,把它們一個個封裝起來,并且使它們可以相互替換;
2.1 策略模式
策略模式的目的就是將算法的使用與算法的實現(xiàn)分離開來,將不變的部分和變化的部分隔開是每個設(shè)計模式的主題;
一個基于策略模式的程序至少由兩部分組成。第一個部分是一組策略類,策略類封裝了具體的算法,并負(fù)責(zé)具體的計算過程。 第二個部分是環(huán)境類 Context, Context 接受客戶的請求,隨后把請求委托給某一個策略類。要做到這點,說明 Context 中要維持對某個策略對象的引用;
實例:積分模式(基礎(chǔ)分乘上對于的等級倍數(shù))
// 1. 最簡單代碼實現(xiàn)
var calculateBonus = function( level, base ){
if ( level === 'S' ){ return base * 4; }
if ( level === 'A' ){ return base * 3; }
};
calculateBonus( 'B', 10 ); // 輸出: 40
calculateBonus( 'S', 5 ); // 輸出: 15
// 1. 模仿傳統(tǒng)面向?qū)ο笳Z言中的策略模式實現(xiàn)
var performanceS = function(){};
performanceS.prototype.calculate = function( base ){
return base * 4;
};
var performanceA = function(){};
performanceA.prototype.calculate = function( base ){
return base * 3;
};
// 定義總分類 Bonus:
var Bonus = function(){
this.base = null; // 原始值
this.strategy = null; // 等級對應(yīng)的策略對象
};
Bonus.prototype.setSalary = function( base ){
this.base = base; // 設(shè)置基礎(chǔ)值
};
Bonus.prototype.setStrategy = function( strategy ){
this.strategy = strategy; // 設(shè)置等級對應(yīng)的策略對象
};
Bonus.prototype.getBonus = function(){ // 取得總分?jǐn)?shù)
return this.strategy.calculate( this.base ); // 把計算分?jǐn)?shù)的操作委托給對應(yīng)的策略對象
};
2.2 JavaScript 版本的策略模式
在 JavaScript 語言中,函數(shù)也是對象,所以更簡單和直接的做法是把 strategy 直接定義為函數(shù):
var strategies = {
"S": function( base ){ return base * 4; },
"A": function( base ){ return base * 3; },
};
var calculateBonus = function( level, base ){
return strategies[ level ]( base );
};
console.log( calculateBonus( 'S', 10 ) ); // 輸出: 40
console.log( calculateBonus( 'A', 5 ) ); // 輸出: 15
2.3 策略模式實現(xiàn)表單校驗
// 步驟1. 封裝策略對象
var strategies = {
isNonEmpty: function( value, errorMsg ){ // 不為空
if ( value === '' ){ return errorMsg ; }
},
minLength: function( value, length, errorMsg ){ // 限制最小長度
if ( value.length < length ){ return errorMsg; }
},
isMobile: function( value, errorMsg ){ // 手機號碼格式
if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){
return errorMsg;
}
}
};
// 步驟2. 實現(xiàn) Validator 類(作為 Context,負(fù)責(zé)接收用戶的請求并委托給 strategy 對象)
var Validator = function(){
this.cache = []; // 保存校驗規(guī)則
};
Validator.prototype.add = function( dom, rule, errorMsg ){
var ary = rule.split( ':' ); // 把 strategy 和參數(shù)分開
this.cache.push(function(){ // 把校驗的步驟用空函數(shù)包裝起來,并且放入 cache
var strategy = ary.shift(); // 用戶挑選的 strategy
ary.unshift( dom.value ); // 把 input 的 value 添加進參數(shù)列表
ary.push( errorMsg ); // 把 errorMsg 添加進參數(shù)列表
return strategies[ strategy ].apply( dom, ary );
});
};
Validator.prototype.start = function(){
for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
var msg = validatorFunc(); // 開始校驗,并取得校驗后的返回信息
if ( msg ){ // 如果有確切的返回值,說明校驗沒有通過
return msg;
}
}
};
// 步驟3. 向 Validator 類發(fā)送校驗的請求
var validataFunc = function(){
var validator = new Validator(); // 創(chuàng)建一個 validator 對象
/***************添加一些校驗規(guī)則****************/
validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能為空' );
validator.add( registerForm.password, 'minLength:6', '密碼長度不能少于 6 位' );
validator.add( registerForm.phoneNumber, 'isMobile', '手機號碼格式不正確' );
var errorMsg = validator.start(); // 獲得校驗結(jié)果
return errorMsg; // 返回校驗結(jié)果
}
var registerForm = document.getElementById( 'registerForm' );
registerForm.onsubmit = function(){
var errorMsg = validataFunc(); // 如果 errorMsg 有確切的返回值,說明未通過校驗
if ( errorMsg ){
alert ( errorMsg );
return false; // 阻止表單提交
}
};
2.4 策略模式的優(yōu)缺點
策略模式的優(yōu)點:
- 策略模式利用組合、委托和多態(tài)等技術(shù)和思想,可以有效地避免多重條件選擇語句;
- 策略模式提供了對開放—封閉原則的完美支持,將算法封裝在獨立的 strategy 中,使得它們易于切換,易于理解,易于擴展;
- 策略模式中的算法也可以復(fù)用在系統(tǒng)的其他地方,從而避免許多重復(fù)的復(fù)制粘貼工作;
- 在策略模式中利用組合和委托來讓 Context 擁有執(zhí)行算法的能力,這也是繼承的一種更輕便的替代方案;
策略模式的缺點:
- 使用策略模式會在程序中增加許多策略類或者策略對象,但實際上這比把它們負(fù)責(zé)的邏輯堆砌在 Context 中要好;
- 使用策略模式,必須了解所有的 strategy,必須了解各個 strategy 之間的不同點,這樣才能選擇一個合適的 strategy ;由于 strategy 要向客戶暴露它的所有實現(xiàn),這是違反最少知識原則;
2.5 一等函數(shù)對象與策略模式
之前的策略模式示例中,既有模擬傳統(tǒng)面向?qū)ο笳Z言的版本,也有針對 JavaScript 語言的特有實現(xiàn)。在以類為中心的傳統(tǒng)面向?qū)ο笳Z言中,不同的算法或者行為被封裝在各個策略類中, Context 將請求委托給這些策略對象,這些策略對象會根據(jù)請求返回不同的執(zhí)行結(jié)果,這樣便能表現(xiàn)出對象的多態(tài)性;
在 JavaScript 中,除了使用類來封裝算法和行為之外,直接使用函數(shù)也是一種選擇。這些“算法”可以被封裝到函數(shù)中并且四處傳遞,也就是常說的“高階函數(shù)”。實際上在 JavaScript 這種將函數(shù)作為一等對象的語言里,策略模式已經(jīng)融入到了語言本身當(dāng)中,我們經(jīng)常用高階函數(shù)來封裝不同的行為,并且把它傳遞到另一個函數(shù)中。當(dāng)我們對這些函數(shù)發(fā)出“調(diào)用”的消息時,不同的函數(shù)會返回不同的執(zhí)行結(jié)果;
var S = function( salary ){ return salary * 4; };
var A = function( salary ){ return salary * 3; };
var B = function( salary ){ return salary * 2; };
var calculateBonus = function( func, salary ){
return func( salary );
};
calculateBonus( S, 10 ); // 輸出: 40
2.6 策略模式小結(jié)
本小節(jié)既有接近傳統(tǒng)面向?qū)ο笳Z言的策略模式實現(xiàn),也有更適合 JavaScript 語言的策略模式版本。在 JavaScript 語言的策略模式中,策略類往往被函數(shù)所代替,這時策略模式就成為一種“隱形”的模式。
系列鏈接
- JavaScript 設(shè)計模式(上)——基礎(chǔ)知識
- JavaScript 設(shè)計模式(中)——1.單例模式
- JavaScript 設(shè)計模式(中)——2.策略模式
- JavaScript 設(shè)計模式(中)——3.代理模式
- JavaScript 設(shè)計模式(中)——4.迭代器模式
- JavaScript 設(shè)計模式(中)——5.發(fā)布訂閱模式
- JavaScript 設(shè)計模式(中)——6.命令模式
- JavaScript 設(shè)計模式(中)——7.組合模式
- JavaScript 設(shè)計模式(中)——8.模板方法模式
- JavaScript 設(shè)計模式(中)——9.享元模式
- JavaScript 設(shè)計模式(中)——10.職責(zé)鏈模式
- JavaScript 設(shè)計模式(中)——11. 中介者模式
- JavaScript 設(shè)計模式(中)——12. 裝飾者模式
- JavaScript 設(shè)計模式(中)——13.狀態(tài)模式
- JavaScript 設(shè)計模式(中)——14.適配器模式
- JavaScript 設(shè)計模式(下)——設(shè)計原則
- JavaScript 設(shè)計模式練習(xí)代碼
本文主要參考了《JavaScript設(shè)計模式和開發(fā)實踐》一書