javascript設(shè)計(jì)模式之策略模式學(xué)習(xí)筆記
1. 理解javascript中的策略模式
策略模式的定義是:定義一系列的算法,把它們一個(gè)個(gè)封裝起來,并且使它們可以相互替換。
使用策略模式的優(yōu)點(diǎn)如下:
優(yōu)點(diǎn):
1. 策略模式利用組合,委托等技術(shù)和思想,有效的避免很多if條件語句。
2. 策略模式提供了開放-封閉原則,使代碼更容易理解和擴(kuò)展。
3. 策略模式中的代碼可以復(fù)用。
一:使用策略模式計(jì)算獎(jiǎng)金;
下面的demo是我在書上看到的,但是沒有關(guān)系,我們只是來理解下策略模式的使用而已,我們可以使用策略模式來計(jì)算獎(jiǎng)金問題;
比如公司的年終獎(jiǎng)是根據(jù)員工的工資和績(jī)效來考核的,績(jī)效為A的人,年終獎(jiǎng)為工資的4倍,績(jī)效為B的人,年終獎(jiǎng)為工資的3倍,績(jī)效為C的人,年終獎(jiǎng)為工資的2倍;現(xiàn)在我們使用一般的編碼方式會(huì)如下這樣編寫代碼:
var calculateBouns = function(salary,level) { if(level === 'A') { return salary * 4; } if(level === 'B') { return salary * 3; } if(level === 'C') { return salary * 2; } }; // 調(diào)用如下: console.log(calculateBouns(4000,'A')); // 16000 console.log(calculateBouns(2500,'B')); // 7500
第一個(gè)參數(shù)為薪資,第二個(gè)參數(shù)為等級(jí);
代碼缺點(diǎn)如下:
calculateBouns 函數(shù)包含了很多if-else語句。
calculateBouns 函數(shù)缺乏彈性,假如還有D等級(jí)的話,那么我們需要在calculateBouns 函數(shù)內(nèi)添加判斷等級(jí)D的if語句;
算法復(fù)用性差,如果在其他的地方也有類似這樣的算法的話,但是規(guī)則不一樣,我們這些代碼不能通用。
2. 使用組合函數(shù)重構(gòu)代碼
組合函數(shù)是把各種算法封裝到一個(gè)個(gè)的小函數(shù)里面,比如等級(jí)A的話,封裝一個(gè)小函數(shù),等級(jí)為B的話,也封裝一個(gè)小函數(shù),以此類推;如下代碼:
var performanceA = function(salary) { return salary * 4; }; var performanceB = function(salary) { return salary * 3; }; var performanceC = function(salary) { return salary * 2 }; var calculateBouns = function(level,salary) { if(level === 'A') { return performanceA(salary); } if(level === 'B') { return performanceB(salary); } if(level === 'C') { return performanceC(salary); } }; // 調(diào)用如下 console.log(calculateBouns('A',4500)); // 18000
代碼看起來有點(diǎn)改善,但是還是有如下缺點(diǎn):
calculateBouns 函數(shù)有可能會(huì)越來越大,比如增加D等級(jí)的時(shí)候,而且缺乏彈性。
3. 使用策略模式重構(gòu)代碼
策略模式指的是 定義一系列的算法,把它們一個(gè)個(gè)封裝起來,將不變的部分和變化的部分隔開,實(shí)際就是將算法的使用和實(shí)現(xiàn)分離出來;算法的使用方式是不變的,都是根據(jù)某個(gè)算法取得計(jì)算后的獎(jiǎng)金數(shù),而算法的實(shí)現(xiàn)是根據(jù)績(jī)效對(duì)應(yīng)不同的績(jī)效規(guī)則;
一個(gè)基于策略模式的程序至少由2部分組成,第一個(gè)部分是一組策略類,策略類封裝了具體的算法,并負(fù)責(zé)具體的計(jì)算過程。第二個(gè)部分是環(huán)境類Context,該Context接收客戶端的請(qǐng)求,隨后把請(qǐng)求委托給某一個(gè)策略類。我們先使用傳統(tǒng)面向?qū)ο髞韺?shí)現(xiàn);
如下代碼:
var performanceA = function(){}; performanceA.prototype.calculate = function(salary) { return salary * 4; }; var performanceB = function(){}; performanceB.prototype.calculate = function(salary) { return salary * 3; }; var performanceC = function(){}; performanceC.prototype.calculate = function(salary) { return salary * 2; }; // 獎(jiǎng)金類 var Bouns = function(){ this.salary = null; // 原始工資 this.levelObj = null; // 績(jī)效等級(jí)對(duì)應(yīng)的策略對(duì)象 }; Bouns.prototype.setSalary = function(salary) { this.salary = salary; // 保存員工的原始工資 }; Bouns.prototype.setlevelObj = function(levelObj){ this.levelObj = levelObj; // 設(shè)置員工績(jī)效等級(jí)對(duì)應(yīng)的策略對(duì)象 }; // 取得獎(jiǎng)金數(shù) Bouns.prototype.getBouns = function(){ // 把計(jì)算獎(jiǎng)金的操作委托給對(duì)應(yīng)的策略對(duì)象 return this.levelObj.calculate(this.salary); }; var bouns = new Bouns(); bouns.setSalary(10000); bouns.setlevelObj(new performanceA()); // 設(shè)置策略對(duì)象 console.log(bouns.getBouns()); // 40000 bouns.setlevelObj(new performanceB()); // 設(shè)置策略對(duì)象 console.log(bouns.getBouns()); // 30000
如上代碼使用策略模式重構(gòu)代碼,可以看到代碼職責(zé)更新分明,代碼變得更加清晰。
4. Javascript版本的策略模式
//代碼如下: var obj = { "A": function(salary) { return salary * 4; }, "B" : function(salary) { return salary * 3; }, "C" : function(salary) { return salary * 2; } }; var calculateBouns =function(level,salary) { return obj[level](salary); }; console.log(calculateBouns('A',10000)); // 40000
可以看到代碼更加簡(jiǎn)單明了;
策略模式指的是定義一系列的算法,并且把它們封裝起來,但是策略模式不僅僅只封裝算法,我們還可以對(duì)用來封裝一系列的業(yè)務(wù)規(guī)則,只要這些業(yè)務(wù)規(guī)則目標(biāo)一致,我們就可以使用策略模式來封裝它們;
表單效驗(yàn)
比如我們經(jīng)常來進(jìn)行表單驗(yàn)證,比如注冊(cè)登錄對(duì)話框,我們登錄之前要進(jìn)行驗(yàn)證操作:比如有以下幾條邏輯:
用戶名不能為空
密碼長(zhǎng)度不能小于6位。
手機(jī)號(hào)碼必須符合格式。
比如HTML代碼如下:
<form action = "http://www.baidu.com" id="registerForm" method = "post"> <p> <label>請(qǐng)輸入用戶名:</label> <input type="text" name="userName"/> </p> <p> <label>請(qǐng)輸入密碼:</label> <input type="text" name="password"/> </p> <p> <label>請(qǐng)輸入手機(jī)號(hào)碼:</label> <input type="text" name="phoneNumber"/> </p> </form>
我們正常的編寫表單驗(yàn)證代碼如下:
var registerForm = document.getElementById("registerForm"); registerForm.onsubmit = function(){ if(registerForm.userName.value === '') { alert('用戶名不能為空'); return; } if(registerForm.password.value.length < 6) { alert("密碼的長(zhǎng)度不能小于6位"); return; } if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) { alert("手機(jī)號(hào)碼格式不正確"); return; } }
但是這樣編寫代碼有如下缺點(diǎn):
1.registerForm.onsubmit 函數(shù)比較大,代碼中包含了很多if語句;
2.registerForm.onsubmit 函數(shù)缺乏彈性,如果增加了一種新的效驗(yàn)規(guī)則,或者想把密碼的長(zhǎng)度效驗(yàn)從6改成8,我們必須改registerForm.onsubmit 函數(shù)內(nèi)部的代碼。違反了開放-封閉原則。
3. 算法的復(fù)用性差,如果在程序中增加了另外一個(gè)表單,這個(gè)表單也需要進(jìn)行一些類似的效驗(yàn),那么我們可能又需要復(fù)制代碼了;
下面我們可以使用策略模式來重構(gòu)表單效驗(yàn);
第一步我們先來封裝策略對(duì)象;如下代碼:
var strategy = { isNotEmpty: function(value,errorMsg) { if(value === '') { return errorMsg; } }, // 限制最小長(zhǎng)度 minLength: function(value,length,errorMsg) { if(value.length < length) { return errorMsg; } }, // 手機(jī)號(hào)碼格式 mobileFormat: function(value,errorMsg) { if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg; } } };
接下來我們準(zhǔn)備實(shí)現(xiàn)Validator類,Validator類在這里作為Context,負(fù)責(zé)接收用戶的請(qǐng)求并委托給strategy 對(duì)象,如下代碼:
var Validator = function(){ this.cache = []; // 保存效驗(yàn)規(guī)則 }; Validator.prototype.add = function(dom,rule,errorMsg) { var str = rule.split(":"); this.cache.push(function(){ // str 返回的是 minLength:6 var strategy = str.shift(); str.unshift(dom.value); // 把input的value添加進(jìn)參數(shù)列表 str.push(errorMsg); // 把errorMsg添加進(jìn)參數(shù)列表 return strategys[strategy].apply(dom,str); }); }; Validator.prototype.start = function(){ for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) { var msg = validatorFunc(); // 開始效驗(yàn) 并取得效驗(yàn)后的返回信息 if(msg) { return msg; } } };
Validator類在這里作為Context,負(fù)責(zé)接收用戶的請(qǐng)求并委托給strategys對(duì)象。上面的代碼中,我們先創(chuàng)建一個(gè)Validator對(duì)象,然后通過validator.add方法往validator對(duì)象中添加一些效驗(yàn)規(guī)則,validator.add方法接收3個(gè)參數(shù),如下代碼:
validator.add(registerForm.password,'minLength:6','密碼長(zhǎng)度不能小于6位');
registerForm.password 為效驗(yàn)的input輸入框dom節(jié)點(diǎn);
minLength:6: 是以一個(gè)冒號(hào)隔開的字符串,冒號(hào)前面的minLength代表客戶挑選的strategys對(duì)象,冒號(hào)后面的數(shù)字6表示在效驗(yàn)過程中所必須驗(yàn)證的參數(shù),minLength:6的意思是效驗(yàn) registerForm.password 這個(gè)文本輸入框的value最小長(zhǎng)度為6位;如果字符串中不包含冒號(hào),說明效驗(yàn)過程中不需要額外的效驗(yàn)信息;
第三個(gè)參數(shù)是當(dāng)效驗(yàn)未通過時(shí)返回的錯(cuò)誤信息;
當(dāng)我們往validator對(duì)象里添加完一系列的效驗(yàn)規(guī)則之后,會(huì)調(diào)用validator.start()方法來啟動(dòng)效驗(yàn)。如果validator.start()返回了一個(gè)errorMsg字符串作為返回值,說明該次效驗(yàn)沒有通過,此時(shí)需要registerForm.onsubmit方法返回false來阻止表單提交。下面我們來看看初始化代碼如下:
var validateFunc = function(){ var validator = new Validator(); // 創(chuàng)建一個(gè)Validator對(duì)象 /* 添加一些效驗(yàn)規(guī)則 */ validator.add(registerForm.userName,'isNotEmpty','用戶名不能為空'); validator.add(registerForm.password,'minLength:6','密碼長(zhǎng)度不能小于6位'); validator.add(registerForm.userName,'mobileFormat','手機(jī)號(hào)碼格式不正確'); var errorMsg = validator.start(); // 獲得效驗(yàn)結(jié)果 return errorMsg; // 返回效驗(yàn)結(jié)果 }; var registerForm = document.getElementById("registerForm"); registerForm.onsubmit = function(){ var errorMsg = validateFunc(); if(errorMsg){ alert(errorMsg); return false; } }
下面是所有的代碼如下:
var strategys = { isNotEmpty: function(value,errorMsg) { if(value === '') { return errorMsg; } }, // 限制最小長(zhǎng)度 minLength: function(value,length,errorMsg) { if(value.length < length) { return errorMsg; } }, // 手機(jī)號(hào)碼格式 mobileFormat: function(value,errorMsg) { if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg; } } }; var Validator = function(){ this.cache = []; // 保存效驗(yàn)規(guī)則 }; Validator.prototype.add = function(dom,rule,errorMsg) { var str = rule.split(":"); this.cache.push(function(){ // str 返回的是 minLength:6 var strategy = str.shift(); str.unshift(dom.value); // 把input的value添加進(jìn)參數(shù)列表 str.push(errorMsg); // 把errorMsg添加進(jìn)參數(shù)列表 return strategys[strategy].apply(dom,str); }); }; Validator.prototype.start = function(){ for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) { var msg = validatorFunc(); // 開始效驗(yàn) 并取得效驗(yàn)后的返回信息 if(msg) { return msg; } } }; var validateFunc = function(){ var validator = new Validator(); // 創(chuàng)建一個(gè)Validator對(duì)象 /* 添加一些效驗(yàn)規(guī)則 */ validator.add(registerForm.userName,'isNotEmpty','用戶名不能為空'); validator.add(registerForm.password,'minLength:6','密碼長(zhǎng)度不能小于6位'); validator.add(registerForm.userName,'mobileFormat','手機(jī)號(hào)碼格式不正確'); var errorMsg = validator.start(); // 獲得效驗(yàn)結(jié)果 return errorMsg; // 返回效驗(yàn)結(jié)果 }; var registerForm = document.getElementById("registerForm"); registerForm.onsubmit = function(){ var errorMsg = validateFunc(); if(errorMsg){ alert(errorMsg); return false; } };
如上使用策略模式來編寫表單驗(yàn)證代碼可以看到好處了,我們通過add配置的方式就完成了一個(gè)表單的效驗(yàn);這樣的話,那么代碼可以當(dāng)做一個(gè)組件來使用,并且可以隨時(shí)調(diào)用,在修改表單驗(yàn)證規(guī)則的時(shí)候,也非常方便,通過傳遞參數(shù)即可調(diào)用;
給某個(gè)文本輸入框添加多種效驗(yàn)規(guī)則,上面的代碼我們可以看到,我們只是給輸入框只能對(duì)應(yīng)一種效驗(yàn)規(guī)則,比如上面的我們只能效驗(yàn)輸入框是否為空,validator.add(registerForm.userName,'isNotEmpty','用戶名不能為空');但是如果我們既要效驗(yàn)輸入框是否為空,還要效驗(yàn)輸入框的長(zhǎng)度不要小于10位的話,那么我們期望需要像如下傳遞參數(shù):
validator.add(registerForm.userName,[{strategy:'isNotEmpty',errorMsg:'用戶名不能為空'},{strategy: 'minLength:6',errorMsg:'用戶名長(zhǎng)度不能小于6位'}])
我們可以編寫代碼如下:
// 策略對(duì)象 var strategys = { isNotEmpty: function(value,errorMsg) { if(value === '') { return errorMsg; } }, // 限制最小長(zhǎng)度 minLength: function(value,length,errorMsg) { if(value.length < length) { return errorMsg; } }, // 手機(jī)號(hào)碼格式 mobileFormat: function(value,errorMsg) { if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg; } } }; var Validator = function(){ this.cache = []; // 保存效驗(yàn)規(guī)則 }; 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 strategys[strategy].apply(dom,strategyAry); }); })(rule); } }; Validator.prototype.start = function(){ for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) { var msg = validatorFunc(); // 開始效驗(yàn) 并取得效驗(yàn)后的返回信息 if(msg) { return msg; } } }; // 代碼調(diào)用 var registerForm = document.getElementById("registerForm"); var validateFunc = function(){ var validator = new Validator(); // 創(chuàng)建一個(gè)Validator對(duì)象 /* 添加一些效驗(yàn)規(guī)則 */ validator.add(registerForm.userName,[ {strategy: 'isNotEmpty',errorMsg:'用戶名不能為空'}, {strategy: 'minLength:6',errorMsg:'用戶名長(zhǎng)度不能小于6位'} ]); validator.add(registerForm.password,[ {strategy: 'minLength:6',errorMsg:'密碼長(zhǎng)度不能小于6位'}, ]); validator.add(registerForm.phoneNumber,[ {strategy: 'mobileFormat',errorMsg:'手機(jī)號(hào)格式不正確'}, ]); var errorMsg = validator.start(); // 獲得效驗(yàn)結(jié)果 return errorMsg; // 返回效驗(yàn)結(jié)果 }; // 點(diǎn)擊確定提交 registerForm.onsubmit = function(){ var errorMsg = validateFunc(); if(errorMsg){ alert(errorMsg); return false; } }
注意:如上代碼都是按照書上來做的,都是看到書的代碼,最主要我們理解策略模式實(shí)現(xiàn),比如上面的表單驗(yàn)證功能是這樣封裝的代碼,我們平時(shí)使用jquery插件表單驗(yàn)證代碼原來是這樣封裝的,為此我們以后也可以使用這種方式來封裝表單等學(xué)習(xí);
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- javascript設(shè)計(jì)模式之策略模式
- JavaScript設(shè)計(jì)模式之策略模式實(shí)現(xiàn)原理詳解
- javascript設(shè)計(jì)模式 – 策略模式原理與用法實(shí)例分析
- JavaScript設(shè)計(jì)模式之策略模式詳解
- 學(xué)習(xí)JavaScript設(shè)計(jì)模式之策略模式
- javascript設(shè)計(jì)模式--策略模式之輸入驗(yàn)證
- 學(xué)習(xí)JavaScript設(shè)計(jì)模式(策略模式)
- JavaScript設(shè)計(jì)模式之策略模式實(shí)例
- JavaScript設(shè)計(jì)模式策略模式案例分享
相關(guān)文章
jJavaScript中toFixed()和正則表達(dá)式的坑
這篇文章主要介紹了jJavaScript中toFixed()和正則表達(dá)式的坑,toFixed方法可以把Number四舍五入為指定小數(shù)位數(shù)的數(shù)字,具體詳細(xì)內(nèi)容需要的小伙伴可以參考一下2022-04-04JS根據(jù)生日月份和日期計(jì)算星座的簡(jiǎn)單實(shí)現(xiàn)方法
這篇文章主要介紹了JS根據(jù)生日月份和日期計(jì)算星座的簡(jiǎn)單實(shí)現(xiàn)方法,涉及javascript日期時(shí)間與字符串操作相關(guān)技巧,需要的朋友可以參考下2016-11-11JavaScript中的this關(guān)鍵字介紹與使用實(shí)例
JavaScript其實(shí)一門基于或者說是面向?qū)ο蟮恼Z言,這樣的話,this這個(gè)關(guān)鍵字,在類的內(nèi)部就顯得尤為重要2013-06-06uniapp踩坑實(shí)戰(zhàn)之文件查找失敗:'uview-ui'?at?main.js解決辦法
這篇文章主要給大家介紹了關(guān)于uniapp踩坑實(shí)戰(zhàn)之文件查找失敗:'uview-ui'?at?main.js的解決辦法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12JavaScript判斷變量是否為空的自定義函數(shù)分享
這篇文章主要介紹了JavaScript判斷變量是否為空的自定義函數(shù)分享,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-01-01使用php的mail()函數(shù)實(shí)現(xiàn)發(fā)送郵件功能
php中的mail()函數(shù)允許您從腳本中直接發(fā)送電子郵件,下面這篇文章主要給大家介紹了關(guān)于如何使用php的mail()函數(shù)實(shí)現(xiàn)發(fā)送郵件功能的相關(guān)資料,需要的朋友可以參考下2021-06-06javascript mouseover、mouseout停止事件冒泡的解決方案
停止事件冒泡在各瀏覽器中已經(jīng)有相應(yīng)的解決方案,但是對(duì)于mouseover和mouseout卻顯得力不從心。2009-04-04滾動(dòng)條響應(yīng)鼠標(biāo)滑輪事件實(shí)現(xiàn)上下滾動(dòng)的js代碼
javascript實(shí)現(xiàn)滾動(dòng)條響應(yīng)鼠標(biāo)滑輪的實(shí)現(xiàn)上下滾動(dòng),示例代碼如下2014-06-06