策略模式的定義是:定義一系列的算法,把它們一個個封裝起來,而且使它們能夠相互替換。
在現實中,若是咱們想去某個地方旅遊,能夠根據實際狀況有多種路線javascript
如今以年終獎的計算爲例html
公司年終獎根據員工的工資基數
和年末績效
來發放java
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
這段代碼簡單,可是存在顯而易見的缺點算法
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
經過使用策略模式重構代碼,消除來原程序中分支語句。全部計算獎金有關的邏輯分佈在策略對象中,每一個策略對象的算法已被各自封裝在對象內部,當咱們對這些策略對象發出「計算獎金」的請求時,它們會返回各自的計算結果,這不只是多態性的體現,也是「自由交換」的目的。app
<!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>
從定義上看,策略模式就是用來封裝算法的。可是若是僅僅用來封裝算法,未免有點大材小用。在實際業務中,策略模式也能夠用來封裝一系列的「業務規則」。只要業務規則指向的目標一致,而且能夠被替換使用,咱們就能夠用策略模式來封裝它們。dom
<!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 } }
使用策略模式重構代碼以後,咱們不只經過「配置」的方式就能夠完成一個表單的校驗,這些規則也能夠複用在程序的任何地方,還能以插件的形式,方便地移植到其餘項目中。而且新增或者修改規則也是絕不費力的。post