1、策略模式的定義是: 定義一系列的算法敛摘, 把它們一個個封裝起來门烂, 并且使它們可以相互替換。 不變的部分和變化的部分隔開是每個設計模式的主題兄淫,策略模式也不例外屯远,策略模式的目的就是將算法的使用與算法的實現(xiàn)分離開來。
2拖叙、一個基于策略模式的程序至少由兩部分組成氓润。第一個部分是一組策略類,策略類封裝了具體的算法薯鳍,并負責具體的計算過程咖气。 第二個部分是環(huán)境類 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(){ // 取得獎金數(shù)額
return this.strategy.calculate( this.salary ); // 把計算獎金的操作委托給對應的策略對象
};
3伶唯、JavaScript 版本的策略模式
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 ;
};
console.log( calculateBonus( 'S', 20000 ) ); // 輸出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 輸出:30000
4、多態(tài)在策略模式中的體現(xiàn)
通過使用策略模式重構代碼惧盹,我們消除了原程序中大片的條件分支語句乳幸。所有跟計算獎金有
關的邏輯不再放在 Context中瞪讼,而是分布在各個策略對象中。Context并沒有計算獎金的能力粹断,而
是把這個職責委托給了某個策略對象符欠。每個策略對象負責的算法已被各自封裝在對象內(nèi)部。當我
們對這些策略對象發(fā)出“計算獎金”的請求時瓶埋,它們會返回各自不同的計算結(jié)果希柿,這正是對象多
態(tài)性的體現(xiàn),也是“它們可以相互替換”的目的养筒。替換 Context中當前保存的策略對象曾撤,便能執(zhí)
行不同的算法來得到我們想要的結(jié)果。
5晕粪、動畫相關
flash移植
var tween = {
linear: function( t, b, c, d ){
return c*t/d + b;
},
easeIn: function( t, b, c, d ){
return c * ( t /= d ) * t + b;
},
strongEaseIn: function(t, b, c, d){
return c * ( t /= d ) * t * t * t * t + b;
},
strongEaseOut: function(t, b, c, d){
return c * ( ( t = t / d - 1) * t * t * t * t + 1 ) + b;
},
sineaseIn: function( t, b, c, d ){
return c * ( t /= d) * t * t + b;
},
sineaseOut: function(t,b,c,d){
return c * ( ( t = t / d - 1) * t * t + 1 ) + b;
}
};
現(xiàn)在進入代碼實現(xiàn)階段挤悉,首先在頁面中放置一個 div:
<body>
<div style="position:absolute;background:blue" id="div">我是 div</div>
</body>
接下來定義 Animate 類, Animate 的構造函數(shù)接受一個參數(shù): 即將運動起來的 dom 節(jié)點兵多。 Animate
類的代碼如下:
var Animate = function( dom ){
this.dom = dom; // 進行運動的 dom 節(jié)點
this.startTime = 0; // 動畫開始時間
this.startPos = 0; // 動畫開始時尖啡,dom 節(jié)點的位置,即 dom 的初始位置
this.endPos = 0; // 動畫結(jié)束時剩膘,dom 節(jié)點的位置衅斩,即 dom 的目標位置
this.propertyName = null; // dom 節(jié)點需要被改變的 css 屬性名
this.easing = null; // 緩動算法
this.duration = null; // 動畫持續(xù)時間
};
Animate.prototype.start = function( propertyName, endPos, duration, easing ){
this.startTime = +new Date; // 動畫啟動時間
this.startPos = this.dom.getBoundingClientRect()[ propertyName ]; // dom 節(jié)點初始位置
this.propertyName = propertyName; // dom 節(jié)點需要被改變的 CSS 屬性名
this.endPos = endPos; // dom 節(jié)點目標位置
this.duration = duration; // 動畫持續(xù)事件
this.easing = tween[ easing ]; // 緩動算法
var self = this;
var timeId = setInterval(function(){ // 啟動定時器,開始執(zhí)行動畫
if ( self.step() === false ){ // 如果動畫已結(jié)束怠褐,則清除定時器
clearInterval( timeId );
}
}, 19 );
};
? propertyName:要改變的 CSS屬性名畏梆,比如'left'、'top'奈懒,分別表示左右移動和上下移動奠涌。
? endPos: 小球運動的目標位置。
? duration: 動畫持續(xù)時間磷杏。
? easing: 緩動算法溜畅。
Animate.prototype.step = function(){
var t = +new Date; // 取得當前時間
if ( t >= this.startTime + this.duration ){ // (1)
this.update( this.endPos ); // 更新小球的 CSS 屬性值
return false;
}
var pos = this.easing( t - this.startTime, this.startPos,
this.endPos - this.startPos, this.duration );
// pos 為小球當前位置
this.update( pos ); // 更新小球的 CSS 屬性值
};
Animate.prototype.update = function( pos ){
this.dom.style[ this.propertyName ] = pos + 'px';
};
var div = document.getElementById( 'div' );
var animate = new Animate( div );
animate.start( 'left', 500, 1000, 'strongEaseOut' );
6、更廣義的“算法”
從定義上看极祸,策略模式就是用來封裝算法的慈格。但如果把策略模式僅僅用來封裝算法,未免有一點大材小用遥金。在實際開發(fā)中浴捆,我們通常會把算法的含義擴散開來,使策略模式也可以用來封裝一系列的“業(yè)務規(guī)則” 稿械。只要這些業(yè)務規(guī)則指向的目標一致选泻,并且可以被替換使用,我們就可以用策略模式來封裝它們。
7页眯、表單校驗
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;
}
}
};
接下來我們準備實現(xiàn) Validator 類梯捕。Validator 類在這里作為 Context,負責接收用戶的請求
并委托給 strategy對象窝撵。 在給出 Validator類的代碼之前科阎, 有必要提前了解用戶是如何向 Validator
類發(fā)送請求的,這有助于我們知道如何去編寫 Validator 類的代碼忿族。代碼如下:
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; // 阻止表單提交
}
};
從這段代碼中可以看到蝌矛,我們先創(chuàng)建了一個 validator 對象道批,然后通過 validator.add 方法,
往 validator 對象中添加一些校驗規(guī)則入撒。 validator.add 方法接受 3個參數(shù)隆豹,以下面這句代碼說明:
validator.add( registerForm.password, 'minLength:6', '密碼長度不能少于 6 位' );
? registerForm.password 為參與校驗的 input 輸入框。
? 'minLength:6'是一個以冒號隔開的字符串茅逮。 冒號前面的minLength代表客戶挑選的strategy
對象璃赡,冒號后面的數(shù)字 6表示在校驗過程中所必需的一些參數(shù)。 'minLength:6'的意思就是
校驗 registerForm.password 這個文本輸入框的 value 最小長度為 6献雅。如果這個字符串中不
包含冒號碉考,說明校驗過程中不需要額外的參數(shù)信息,比如'isNonEmpty'挺身。
? 第 3個參數(shù)是當校驗未通過時返回的錯誤信息侯谁。
當我們往 validator 對象里添加完一系列的校驗規(guī)則之后,會調(diào)用 validator.start()方法來
啟動校驗章钾。如果 validator.start()返回了一個確切的 errorMsg 字符串當作返回值墙贱,說明該次校驗
沒有通過,此時需讓 registerForm.onsubmit 方法返回 false 來阻止表單的提交贱傀。
后是 Validator 類的實現(xiàn):
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;
}
}
};
使用策略模式重構代碼之后魁衙,我們僅僅通過“配置”的方式就可以完成一個表單的校驗,這些校驗規(guī)則也可以復用在程序的任何地方椰棘,還能作為插件的形式纺棺,方便地被移植到其他項目中。 在修改某個校驗規(guī)則的時候邪狞,只需要編寫或者改寫少量的代碼祷蝌。比如我們想將用戶名輸入框的校驗規(guī)則改成用戶名不能少于 4個字符》浚可以看到巨朦,這時候的修改是毫不費力的米丘。代碼如下:
validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能為空' );
// 改成:
validator.add( registerForm.userName, 'minLength:10', '用戶名長度不能小于 10 位' );
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;
}
}
};
8、給某個文本輸入框添加多種校驗規(guī)則
/***********************策略對象**************************/
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;
}
}
};
/***********************Validator 類**************************/
var Validator = function(){
this.cache = [];
};
Validator.prototype.add = function( dom, rules ){
var self = this;
for ( var i = 0, rule; rule = rules[ i++ ]; ){
(function( rule ){
var strategyAry = rule.strategy.split( ':' );
var errorMsg = rule.errorMsg;
self.cache.push(function(){
var strategy = strategyAry.shift();
strategyAry.unshift( dom.value );
strategyAry.push( errorMsg );
return strategies[ strategy ].apply( dom, strategyAry );
});
})( rule )
}
};
Validator.prototype.start = function(){
for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
var errorMsg = validatorFunc();
if ( errorMsg ){
return errorMsg;
}
}
};
/***********************客戶調(diào)用代碼**************************/
var registerForm = document.getElementById( 'registerForm' );
var validataFunc = function(){
var validator = new Validator();
validator.add( registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用戶名不能為空'
}, {
strategy: 'minLength:6',
errorMsg: '用戶名長度不能小于 10 位'
}]);
validator.add( registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密碼長度不能小于 6 位'
}]);
validator.add( registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手機號碼格式不正確'
}]);
var errorMsg = validator.start();
return errorMsg;
}
registerForm.onsubmit = function(){
var errorMsg = validataFunc();
if ( errorMsg ){
alert ( errorMsg );
return false;
}
};
9棚蓄、策略模式優(yōu)缺點
? 策略模式利用組合堕扶、委托和多態(tài)等技術和思想,可以有效地避免多重條件選擇語句梭依。
? 策略模式提供了對開放—封閉原則的完美支持稍算,將算法封裝在獨立的 strategy 中,使得它們易于切換役拴,易于理解糊探,易于擴展。
? 策略模式中的算法也可以復用在系統(tǒng)的其他地方河闰,從而避免許多重復的復制粘貼工作科平。
? 在策略模式中利用組合和委托來讓 Context擁有執(zhí)行算法的能力,這也是繼承的一種更輕
便的替代方案姜性。
當然瞪慧,策略模式也有一些缺點,但這些缺點并不嚴重部念。
首先汞贸,使用策略模式會在程序中增加許多策略類或者策略對象,但實際上這比把它們負責的
邏輯堆砌在 Context中要好印机。
其次矢腻,要使用策略模式,必須了解所有的 strategy射赛,必須了解各個 strategy 之間的不同點多柑,這樣才能選擇一個合適的 strategy。比如楣责,我們要選擇一種合適的旅游出行路線竣灌,必須先了解選擇飛機、火車秆麸、自行車等方案的細節(jié)初嘹。此時 strategy 要向客戶暴露它的所有實現(xiàn),這是違反最少知識原則的沮趣。