設計模式——策略模式

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),這是違反最少知識原則的沮趣。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屯烦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驻龟,老刑警劉巖温眉,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異翁狐,居然都是意外死亡类溢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進店門露懒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闯冷,“玉大人,你說我怎么就攤上這事懈词∏远悖” “怎么了?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵钦睡,是天一觀的道長。 經(jīng)常有香客問我躁倒,道長荞怒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任秧秉,我火速辦了婚禮褐桌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘象迎。我一直安慰自己荧嵌,他們只是感情好,可當我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布砾淌。 她就那樣靜靜地躺著啦撮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汪厨。 梳的紋絲不亂的頭發(fā)上赃春,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天,我揣著相機與錄音劫乱,去河邊找鬼织中。 笑死,一個胖子當著我的面吹牛衷戈,可吹牛的內(nèi)容都是我干的狭吼。 我是一名探鬼主播,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼殖妇,長吁一口氣:“原來是場噩夢啊……” “哼刁笙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤采盒,失蹤者是張志新(化名)和其女友劉穎旧乞,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磅氨,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡尺栖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了烦租。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片延赌。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叉橱,靈堂內(nèi)的尸體忽然破棺而出挫以,到底是詐尸還是另有隱情,我是刑警寧澤窃祝,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布掐松,位于F島的核電站,受9級特大地震影響粪小,放射性物質(zhì)發(fā)生泄漏大磺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一探膊、第九天 我趴在偏房一處隱蔽的房頂上張望杠愧。 院中可真熱鬧,春花似錦逞壁、人聲如沸流济。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绳瘟。三九已至,卻和暖如春姿骏,著一層夾襖步出監(jiān)牢的瞬間稽荧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工工腋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姨丈,地道東北人。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓擅腰,卻偏偏與公主長得像蟋恬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子趁冈,可洞房花燭夜當晚...
    茶點故事閱讀 43,566評論 2 349

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:https://www.cnblogs.com/tugenhua0707/p/4722696.html理解j...
    Picidae閱讀 302評論 0 1
  • 單例模式 適用場景:可能會在場景中使用到對象歼争,但只有一個實例拜马,加載時并不主動創(chuàng)建,需要時才創(chuàng)建 最常見的單例模式沐绒,...
    Obeing閱讀 2,058評論 1 10
  • 策略模式的定義是:定義一系列的算法俩莽,把它們一個個封裝起來,并且使它們可以相互替換乔遮。 俗話說扮超,條條大路通羅馬。在美劇...
    梅梅_1461閱讀 418評論 0 2
  • 介紹 策略模式的定義是:定義一系列的算法蹋肮,把它們一個個封裝起來出刷,并且使它們可以相互替換。 在程序設計中坯辩,我們也常常...
    悟空你又瘦了閱讀 607評論 0 2