學(xué)習(xí)JavaScript設(shè)計(jì)模式(策略模式)
何為策略?比如我們要去某個(gè)地方旅游,可以根據(jù)具體的實(shí)際情況來(lái)選擇出行的線路。
1、策略模式的定義
如果沒(méi)有時(shí)間但是不在乎錢,可以選擇坐飛機(jī)。
如果沒(méi)有錢,可以選擇坐大巴或者火車。
如果再窮一點(diǎn),可以選擇騎自行車。
在程序設(shè)計(jì)中,我們也常常遇到類似的情況,要實(shí)現(xiàn)某一個(gè)功能有多種方案可以選擇。比如一個(gè)壓縮文件的程序,既可以選擇zip算法,也可以選擇gzip算法。
定義:策略模式定義一系列的算法,分別封裝起來(lái),讓他們之間可以互相替換,此模式讓算法的變化獨(dú)立于使用算飯的客戶.
策略模式有著廣泛的應(yīng)用。本節(jié)我們就以年終獎(jiǎng)的計(jì)算為例進(jìn)行介紹。
2、年終獎(jiǎng)實(shí)例
很多公司的年終獎(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)
我們可以編寫(xiě)一個(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)這段代碼。
2). 使用組合函數(shù)重構(gòu)代碼
一般最容易想到的辦法就是使用組合函數(shù)來(lái)重構(gòu)它,我們把各種算法封裝到一個(gè)個(gè)的小函數(shù)里面,這些小函數(shù)有著良好的命名,可以一目了然地知道它對(duì)應(yīng)著哪種算法,它們也可以被復(fù)用在程序的其他地方。代碼如下:
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
目前,我們的程序得到了一定的改善,但這種改善非常有限,我們依然沒(méi)有解決最重要的問(wèn)題:calculateBonus函數(shù)有可能越來(lái)越龐大,而且在系統(tǒng)變化的時(shí)候缺乏彈性。
3). 使用策略模式重構(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)上面的代碼。第一個(gè)版本是模仿傳統(tǒng)面向?qū)ο笳Z(yǔ)言中的實(shí)現(xiàn)。我們先把每種績(jī)效的計(jì)算規(guī)則都封裝在對(duì)應(yīng)的策略類里面:
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; };
接下來(lái)定義獎(jiǎng)金類Bonus:
var Bonus = function(){ this.salary = null; //原始工資 this.strategy = null; //績(jī)效等級(jí)對(duì)應(yīng)的策略對(duì)象 }; Bonus.prototype.setSalary = function( salary ){ this.salary = salary; //設(shè)置員工的原始工資 }; Bonus.prototype.setStrategy = function( strategy ){ this.strategy = strategy; //設(shè)置員工績(jī)效等級(jí)對(duì)應(yīng)的策略對(duì)象 }; Bonus.prototype.getBonus = function(){ //取得獎(jiǎng)金數(shù)額 return this.strategy.calculate( this.salary ); //把計(jì)算獎(jiǎng)金的操作委托給對(duì)應(yīng)的策略對(duì)象 };
在完成最終的代碼之前,我們?cè)賮?lái)回顧一下策略模式的思想:
定義一系列的算法,把它們一個(gè)個(gè)封裝起來(lái),并且使它們可以相互替換。
這句話如果說(shuō)得更詳細(xì)一點(diǎn),就是:定義一系列的算法,把它們各自封裝成策略類,算法被封裝在策略類內(nèi)部的方法里。在客戶對(duì)Context發(fā)起請(qǐng)求的時(shí)候,Context總是把請(qǐng)求委托給這些策略對(duì)象中間的某一個(gè)進(jìn)行計(jì)算。
“并且使它們可以相互替換”,這句話在很大程度上是相對(duì)于靜態(tài)類型語(yǔ)言而言的。因?yàn)殪o態(tài)類型語(yǔ)言中有類型檢查機(jī)制,所以各個(gè)策略類需要實(shí)現(xiàn)同樣的接口。當(dāng)它們的真正類型被隱藏在接口后面時(shí),它們才能被相互替換。而在JavaScript這種“類型模糊”的語(yǔ)言中沒(méi)有這種困擾,任何對(duì)象都可以被替換使用。因此,JavaScript中的“可以相互替換使用”表現(xiàn)為它們具有相同的目標(biāo)和意圖。
現(xiàn)在我們來(lái)完成這個(gè)例子中剩下的代碼。先創(chuàng)建一個(gè)bonus對(duì)象,并且給bonus對(duì)象設(shè)置一些原始的數(shù)據(jù),比如員工的原始工資數(shù)額。接下來(lái)把某個(gè)計(jì)算獎(jiǎng)金的策略對(duì)象也傳入bonus對(duì)象內(nèi)部保存起來(lái)。當(dāng)調(diào)用bonus.getBonus()來(lái)計(jì)算獎(jiǎng)金的時(shí)候,bonus對(duì)象本身并沒(méi)有能力進(jìn)行計(jì)算,而是把請(qǐng)求委托給了之前保存好的策略對(duì)象:
var bonus = new Bonus(); bonus.setSalary( 10000 ); bonus.setStrategy( new performanceS() ); //設(shè)置策略對(duì)象 console.log( bonus.getBonus() ); // 輸出:40000 bonus.setStrategy( new performanceA() ); //設(shè)置策略對(duì)象 console.log( bonus.getBonus() ); // 輸出:30000
剛剛我們用策略模式重構(gòu)了這段計(jì)算年終獎(jiǎng)的代碼,可以看到通過(guò)策略模式重構(gòu)之后,代碼變得更加清晰,各個(gè)類的職責(zé)更加鮮明。但這段代碼是基于傳統(tǒng)面向?qū)ο笳Z(yǔ)言的模仿,下一節(jié)我們將了解用JavaScript實(shí)現(xiàn)的策略模式。
在5.1節(jié)中,我們讓strategy對(duì)象從各個(gè)策略類中創(chuàng)建而來(lái),這是模擬一些傳統(tǒng)面向?qū)ο笳Z(yǔ)言的實(shí)現(xiàn)。實(shí)際上在JavaScript語(yǔ)言中,函數(shù)也是對(duì)象,所以更簡(jiǎn)單和直接的做法是把strategy直接定義為函數(shù):
var strategies = { "S": function( salary ){ return salary * 4; }, "A": function( salary ){ return salary * 3; }, "B": function( salary ){ return salary * 2; } };
同樣,Context也沒(méi)有必要必須用Bonus類來(lái)表示,我們依然用calculateBonus 函數(shù)充當(dāng)Context來(lái)接受用戶的請(qǐng)求。經(jīng)過(guò)改造,代碼的結(jié)構(gòu)變得更加簡(jiǎn)潔:
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
3、實(shí)例再講解
一個(gè)小例子就能讓我們一目了然。
回憶下jquery里的animate方法.
$( div ).animate( {"left: 200px"}, 1000, 'linear' ); //勻速運(yùn)動(dòng) $( div ).animate( {"left: 200px"}, 1000, 'cubic' ); //三次方的緩動(dòng)
這2句代碼都是讓div在1000ms內(nèi)往右移動(dòng)200個(gè)像素. linear(勻速)和cubic(三次方緩動(dòng))就是一種策略模式的封裝.
再來(lái)一個(gè)例子. 很多頁(yè)面都會(huì)有個(gè)即時(shí)驗(yàn)證的表單. 表單的每個(gè)成員都會(huì)有一些不同的驗(yàn)證規(guī)則.
比如姓名框里面, 需要驗(yàn)證非空,敏感詞,字符過(guò)長(zhǎng)這幾種情況。 當(dāng)然是可以寫(xiě)3個(gè)if else來(lái)解決,不過(guò)這樣寫(xiě)代碼的擴(kuò)展性和維護(hù)性可想而知。如果表單里面的元素多一點(diǎn),需要校驗(yàn)的情況多一點(diǎn),加起來(lái)寫(xiě)上百個(gè)if else也不是沒(méi)有可能。
所以更好的做法是把每種驗(yàn)證規(guī)則都用策略模式單獨(dú)的封裝起來(lái)。需要哪種驗(yàn)證的時(shí)候只需要提供這個(gè)策略的名字。就像這樣:
nameInput.addValidata({ notNull: true, dirtyWords: true, maxLength: 30 }) 而notNull,maxLength等方法只需要統(tǒng)一的返回true或者false,來(lái)表示是否通過(guò)了驗(yàn)證。 validataList = { notNull: function( value ){ return value !== ''; }, maxLength: function( value, maxLen ){ return value.length() > maxLen; } }
可以看到,各種驗(yàn)證規(guī)則很容易被修改和相互替換。如果某天產(chǎn)品經(jīng)理建議字符過(guò)長(zhǎng)的限制改成60個(gè)字符。那只需要0.5秒完成這次工作。
大概內(nèi)容就為大家介紹到這。
聊一聊題外話,馬上2015年要過(guò)去了,大家的年終獎(jiǎng)是不是很豐厚呀?。?!
希望大家可以在這一年里有所收獲,通過(guò)這篇文章也能有所收獲,知道什么是策略模式,理解小編精心為大家準(zhǔn)備的兩個(gè)實(shí)例。
- js單例模式的兩種方案
- JS實(shí)現(xiàn)單例模式的6種方案匯總
- javascript 單例模式演示代碼 javascript面向?qū)ο缶幊?/a>
- JavaScript的單例模式 (singleton in Javascript)
- 輕松掌握J(rèn)avaScript單例模式
- [js高手之路]單例模式實(shí)現(xiàn)模態(tài)框的示例
- JavaScript設(shè)計(jì)模式之策略模式詳解
- javascript設(shè)計(jì)模式--策略模式之輸入驗(yàn)證
- JavaScript設(shè)計(jì)模式之策略模式實(shí)例
- 學(xué)習(xí)JavaScript設(shè)計(jì)模式之策略模式
- javascript單例模式與策略模式實(shí)例詳解
相關(guān)文章
JS Range HTML文檔/文字內(nèi)容選中、庫(kù)及應(yīng)用介紹
本文的內(nèi)容基本上是基于“區(qū)域范圍對(duì)象(Range objects)”這個(gè)概念來(lái)說(shuō)的2011-05-05兩種js監(jiān)聽(tīng)滾輪事件的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇兩種js監(jiān)聽(tīng)滾輪事件的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05JavaScript 異步調(diào)用框架 (Part 6 - 實(shí)例 & 模式)
我們用了5篇文章來(lái)討論如何編寫(xiě)一個(gè)JavaScript異步調(diào)用框架(問(wèn)題 & 場(chǎng)景、用例設(shè)計(jì)、代碼實(shí)現(xiàn)、鏈?zhǔn)秸{(diào)用、鏈?zhǔn)綄?shí)現(xiàn)),現(xiàn)在是時(shí)候讓我們看一下在各種常見(jiàn)開(kāi)發(fā)情景中如何使用它了。2009-08-08淺談Javascript實(shí)現(xiàn)繼承的方法
本文給大家簡(jiǎn)單介紹了下如何在javascript中實(shí)現(xiàn)繼承的幾種方法,十分的實(shí)用,有需要的小伙伴可以參考下。2015-07-07TypeScript?背后的結(jié)構(gòu)化類型系統(tǒng)原理詳解
這篇文章主要為大家介紹了TypeScript?背后的結(jié)構(gòu)化類型系統(tǒng)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11