Javascript設計模式(四)策略模式
策略模式的定義是:定義一系列的演算法,把它們一個個封裝起來,並且使它們可以相互替換。
在現實中,如果我們想去某個地方旅遊,可以根據實際情況有多種路線
- 如果沒有時間但是不在乎錢,可以選擇飛機
- 如果沒有錢,可以選擇大巴活著火車
- 如果再窮一點,可以選擇騎自行車
使用策略模式計算獎金
現在以年終獎的計算為例
公司年終獎根據員工的工資基數
和年底績效
來發放
- 績效S,四倍年終獎
- 績效A,三倍年終獎
- 績效B,二倍年終獎
最初的實現
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', 2000) // 4000 calculateBonus('S', 2000) // 8000 複製程式碼
這段程式碼簡單,但是存在顯而易見的缺點
- 函式比較龐大,包含很多if-else語句,這些語句需要覆蓋所有的邏輯分支
- 缺乏彈性,如果想新增績效C,就得深入函式內部實現,違反開放-封閉原則
- 演算法的複用性差
策略模式的實現
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', 2000)) // 8000 console.log(calculateBonus('B', 2000)) // 4000 複製程式碼
通過使用策略模式重構程式碼,消除來原程式中分支語句。所有計算獎金有關的邏輯分佈在策略物件中,每個策略物件的演算法已被各自封裝在物件內部,當我們對這些策略物件發出“計算獎金”的請求時,它們會返回各自的計算結果,這不僅是多型性的體現,也是“自由交換”的目的。
使用策略模式實現緩動動畫
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1"> <title></title> </head> <body> <div id="div" style="background: #141527;display: inline-block;position: relative;">我說div</div> <script type="text/javascript"> /** * 緩動演算法 * @t 已消耗的時間 * @b 小球原始位置 * @c 小球目標位置 * @d 動畫持續的總時間 */ 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;}, easeOut: function(t,b,c,d){ return -c *(t/=d)*(t-2) + b;}, easeInOut: function(t,b,c,d){ if ((t/=d/2) < 1) return c/2*t*t + b; return -c/2 * ((--t)*(t-2) - 1) + b; } } /** * 定義Animate類 */ var Animate = function(dom) { this.dom = dom;// 運動的元素 this.startTime = 0;// 動畫開始時間 this.startPos = 0;// 元素初始位置 this.endPos = 0;// 元素結束位置 this.propertyName = '';// 實現動畫的元素屬性 this.easing = null;// 緩動演算法 this.duration = 0;// 動畫持續的時間 }; /** * 啟動動畫 */ Animate.prototype.start = function(propertyName, endPos, duration, easing) { this.startTime = new Date();// 初始化動畫開始的時間 this.startPos = this.dom.getBoundingClientRect()[propertyName]; this.propertyName = propertyName; this.endPos = endPos; this.duration = duration; this.easing = tween[easing];// 緩動演算法 var that = this; var timed = setInterval(function() { // 執行每幀操作 if(that.step() === false) {// 動畫已結束 clearInterval(timed); } }, 19); }; // 判斷當前動畫狀態,呼叫update Animate.prototype.step = function() { var t = new Date();// 執行動畫的當前時間 if( t.getTime() >= this.startTime.getTime() + this.duration) {// 動畫已結束 this.update(this.endPos); return false; } var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration); this.update(pos); }; // 計算位置更新屬性 Animate.prototype.update = function(pos) { this.dom.style[this.propertyName] = pos + 'px'; }; var div = document.getElementById('div') var animate = new Animate(div) animate.start('top', 500, 1000, 'easeInOut') </script> </body> </html> 複製程式碼
用策略模式實現表單驗證
從定義上看,策略模式就是用來封裝演算法的。但是如果僅僅用來封裝演算法,未免有點大材小用。在實際業務中,策略模式也可以用來封裝一系列的“業務規則”。只要業務規則指向的目標一致,並且可以被替換使用,我們就可以用策略模式來封裝它們。
普通版本的表單驗證
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1"> <title></title> </head> <body> <form action="http://xxx.com/register" id="registerForm" method="post"> 請輸入使用者名稱<input type="text" name="username"/><br /> 請輸入密碼<input type="text" name="password"/><br /> 請輸入手機號<input type="text" name="phonenumber"/><br /> <input type="submit" value="提交" style="padding: 10px 20px;"> </form> <script type="text/javascript"> var registerForm = document.getElementById('registerForm') registerForm.onsubmit = function() { if (registerForm.username.value === '') { alert('使用者名稱不能為空') return false } if (registerForm.password.value.length < 6) { alert('密碼長度不能小於6位') return false } if (!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phonenumber.value) ){ alert('手機號碼格式不正確') return false } } </script> </body> </html> 複製程式碼
這是一種很常見的編碼方式,可以看到缺點和計算獎金一摸一樣
用策略模式重構表單驗證
- 很明顯第一步我們需要將驗證邏輯封裝成策略物件
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
類,負責接受使用者的請求並委託給strategies
var Validator = function() { //儲存校驗規則 this.cache = [] } // 新增校驗 Validator.prototype.add = function(dom, rules) { var self = this // 遍歷校驗規則 for(var i = 0, rule; rule = rules[i++];) { (function(rule){ //把strategy和引數分開 var strategyAry = rule.strategy.split(':') var errorMsg = rule.errorMsg // 把校驗的步驟用空函式包裝起來,並且放入cache self.cache.push(function(){ // 挑選出校驗規則 var strategy = strategyAry.shift() // 把input的value新增進引數列表 strategyAry.unshift(dom.value) // 把errorMsg新增進引數列表 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 } } } 複製程式碼
- 接下來就是呼叫了
var registerForm = document.getElementById('registerForm') var validataFunc = function() { var validator = new Validator() validator.add(registerForm.username, [ { strategy: 'isNonEmpty', errorMsg: '使用者名稱不能為空' }, { strategy: 'minLength:10', errorMsg: '使用者名稱長度不能小於10位' } ] ) validator.add(registerForm.password, [ { strategy: 'minLength:6', errorMsg: '密碼長度不能小於6位' } ] ) validator.add(registerForm.phonenumber, [ { strategy: 'isMobile', errorMsg: '手機號碼格式不正確' } ] ) var errorMsg = validator.start() return errorMsg } var sub = document.querySelector('input[type="submit"]') sub.onclick = function() { var errorMsg = validataFunc() if (errorMsg) { console.error(errorMsg) return false } } 複製程式碼
使用策略模式重構程式碼之後,我們不僅通過“配置”的方式就可以完成一個表單的校驗,這些規則也可以複用在程式的任何地方,還能以外掛的形式,方便地移植到其他專案中。並且新增或者修改規則也是毫不費力的。
策略模式的優缺點
優點
- 策略模式利用組合,委託和多型等技術思想,可以有效避免多重條件選擇語句。
- 策略模式提供了對開放-封閉原則的完美支援。將演算法封裝在獨立的strategy中,使得它們易於切換,易於理解,易於擴充套件。
- 策略模式中的演算法也可以複用在系統中的其他地方。
- 在策略模式中利用組合和委託讓Content擁有執行演算法的能力,這也是繼承的一種更輕便的替代方案。
缺點
- 使用策略物件會增加很多策略類或者策略物件,但實際上比把這些邏輯放在Content更好。
- 策略模式會向用戶暴露所有實現細節,這其實是違反最少知識原則。