javascript策略者模式

轉(zhuǎn)載 : https://www.cnblogs.com/tugenhua0707/p/4722696.html
理解javascript中的策略模式

策略模式的定義是:定義一系列的算法,把它們一個個封裝起來,并且使它們可以相互替換。

使用策略模式的優(yōu)點(diǎn)如下:

優(yōu)點(diǎn):1. 策略模式利用組合,委托等技術(shù)和思想,有效的避免很多if條件語句。

    2. 策略模式提供了開放-封閉原則,使代碼更容易理解和擴(kuò)展。

    3. 策略模式中的代碼可以復(fù)用。

一:使用策略模式計算獎金;

下面的demo是我在書上看到的,但是沒有關(guān)系,我們只是來理解下策略模式的使用而已,我們可以使用策略模式來計算獎金問題;

比如公司的年終獎是根據(jù)員工的工資和績效來考核的,績效為A的人,年終獎為工資的4倍,績效為B的人,年終獎為工資的3倍,績效為C的人,年終獎為工資的2倍;現(xiàn)在我們使用一般的編碼方式會如下這樣編寫代碼:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">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</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

第一個參數(shù)為薪資,第二個參數(shù)為等級;

代碼缺點(diǎn)如下:

  1. calculateBouns 函數(shù)包含了很多if-else語句。
  2. calculateBouns 函數(shù)缺乏彈性,假如還有D等級的話,那么我們需要在calculateBouns 函數(shù)內(nèi)添加判斷等級D的if語句;
  3. 算法復(fù)用性差,如果在其他的地方也有類似這樣的算法的話,但是規(guī)則不一樣,我們這些代碼不能通用。

2. 使用組合函數(shù)重構(gòu)代碼

組合函數(shù)是把各種算法封裝到一個個的小函數(shù)里面,比如等級A的話,封裝一個小函數(shù),等級為B的話,也封裝一個小函數(shù),以此類推;如下代碼:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">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</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

代碼看起來有點(diǎn)改善,但是還是有如下缺點(diǎn):

calculateBouns 函數(shù)有可能會越來越大,比如增加D等級的時候,而且缺乏彈性。

3. 使用策略模式重構(gòu)代碼

策略模式指的是 定義一系列的算法,把它們一個個封裝起來,將不變的部分和變化的部分隔開,實(shí)際就是將算法的使用和實(shí)現(xiàn)分離出來;算法的使用方式是不變的,都是根據(jù)某個算法取得計算后的獎金數(shù),而算法的實(shí)現(xiàn)是根據(jù)績效對應(yīng)不同的績效規(guī)則;

一個基于策略模式的程序至少由2部分組成,第一個部分是一組策略類,策略類封裝了具體的算法,并負(fù)責(zé)具體的計算過程。第二個部分是環(huán)境類Context,該Context接收客戶端的請求,隨后把請求委托給某一個策略類。我們先使用傳統(tǒng)面向?qū)ο髞韺?shí)現(xiàn);

如下代碼:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">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;
}; // 獎金類
var Bouns = function(){ this.salary = null; // 原始工資
this.levelObj = null; // 績效等級對應(yīng)的策略對象
};
Bouns.prototype.setSalary = function(salary) { this.salary = salary; // 保存員工的原始工資
};
Bouns.prototype.setlevelObj = function(levelObj){ this.levelObj = levelObj; // 設(shè)置員工績效等級對應(yīng)的策略對象
}; // 取得獎金數(shù)
Bouns.prototype.getBouns = function(){ // 把計算獎金的操作委托給對應(yīng)的策略對象
return this.levelObj.calculate(this.salary);
}; var bouns = new Bouns();
bouns.setSalary(10000);
bouns.setlevelObj(new performanceA()); // 設(shè)置策略對象
console.log(bouns.getBouns()); // 40000
bouns.setlevelObj(new performanceB()); // 設(shè)置策略對象
console.log(bouns.getBouns()); // 30000</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

如上代碼使用策略模式重構(gòu)代碼,可以看到代碼職責(zé)更新分明,代碼變得更加清晰。

4. Javascript版本的策略模式

代碼如下:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">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 objlevel;
};
console.log(calculateBouns('A',10000)); // 40000</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

可以看到代碼更加簡單明了;

策略模式指的是定義一系列的算法,并且把它們封裝起來,但是策略模式不僅僅只封裝算法,我們還可以對用來封裝一系列的業(yè)務(wù)規(guī)則,只要這些業(yè)務(wù)規(guī)則目標(biāo)一致,我們就可以使用策略模式來封裝它們;

表單效驗

比如我們經(jīng)常來進(jìn)行表單驗證,比如注冊登錄對話框,我們登錄之前要進(jìn)行驗證操作:比如有以下幾條邏輯:

  1. 用戶名不能為空
  2. 密碼長度不能小于6位。
  3. 手機(jī)號碼必須符合格式。

比如HTML代碼如下:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;"><form action = "http://www.baidu.com" id="registerForm" method = "post">
<p>
<label>請輸入用戶名:</label>
<input type="text" name="userName"/>
</p>
<p>
<label>請輸入密碼:</label>
<input type="text" name="password"/>
</p>
<p>
<label>請輸入手機(jī)號碼:</label>
<input type="text" name="phoneNumber"/>
</p>
</form></pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

我們正常的編寫表單驗證代碼如下:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){ if(registerForm.userName.value === '') {
alert('用戶名不能為空'); return;
} if(registerForm.password.value.length < 6) {
alert("密碼的長度不能小于6位"); return;
} if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
alert("手機(jī)號碼格式不正確"); return;
}
}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

但是這樣編寫代碼有如下缺點(diǎn):

  1. registerForm.onsubmit 函數(shù)比較大,代碼中包含了很多if語句;
  2. registerForm.onsubmit 函數(shù)缺乏彈性,如果增加了一種新的效驗規(guī)則,或者想把密碼的長度效驗從6改成8,我們必須改registerForm.onsubmit 函數(shù)內(nèi)部的代碼。違反了開放-封閉原則。
  3. 算法的復(fù)用性差,如果在程序中增加了另外一個表單,這個表單也需要進(jìn)行一些類似的效驗,那么我們可能又需要復(fù)制代碼了;

下面我們可以使用策略模式來重構(gòu)表單效驗;

第一步我們先來封裝策略對象;如下代碼:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var strategy = {
isNotEmpty: function(value,errorMsg) { if(value === '') { return errorMsg;
}
}, // 限制最小長度
minLength: function(value,length,errorMsg) { if(value.length < length) { return errorMsg;
}
}, // 手機(jī)號碼格式
mobileFormat: function(value,errorMsg) { if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg;
}
}
};</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

接下來我們準(zhǔn)備實(shí)現(xiàn)Validator類,Validator類在這里作為Context,負(fù)責(zé)接收用戶的請求并委托給strategy 對象,如下代碼:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var Validator = function(){ this.cache = []; // 保存效驗規(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(); // 開始效驗 并取得效驗后的返回信息
if(msg) { return msg;
}
}
};</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

Validator類在這里作為Context,負(fù)責(zé)接收用戶的請求并委托給strategys對象。上面的代碼中,我們先創(chuàng)建一個Validator對象,然后通過validator.add方法往validator對象中添加一些效驗規(guī)則,validator.add方法接收3個參數(shù),如下代碼:

validator.add(registerForm.password,'minLength:6','密碼長度不能小于6位');

registerForm.password 為效驗的input輸入框dom節(jié)點(diǎn);

minLength:6: 是以一個冒號隔開的字符串,冒號前面的minLength代表客戶挑選的strategys對象,冒號后面的數(shù)字6表示在效驗過程中所必須驗證的參數(shù),minLength:6的意思是效驗 registerForm.password 這個文本輸入框的value最小長度為6位;如果字符串中不包含冒號,說明效驗過程中不需要額外的效驗信息;

第三個參數(shù)是當(dāng)效驗未通過時返回的錯誤信息;

當(dāng)我們往validator對象里添加完一系列的效驗規(guī)則之后,會調(diào)用validator.start()方法來啟動效驗。如果validator.start()返回了一個errorMsg字符串作為返回值,說明該次效驗沒有通過,此時需要registerForm.onsubmit方法返回false來阻止表單提交。下面我們來看看初始化代碼如下:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var validateFunc = function(){ var validator = new Validator(); // 創(chuàng)建一個Validator對象
/* 添加一些效驗規(guī)則 */ validator.add(registerForm.userName,'isNotEmpty','用戶名不能為空');
validator.add(registerForm.password,'minLength:6','密碼長度不能小于6位');
validator.add(registerForm.userName,'mobileFormat','手機(jī)號碼格式不正確'); var errorMsg = validator.start(); // 獲得效驗結(jié)果
return errorMsg; // 返回效驗結(jié)果
}; var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){ var errorMsg = validateFunc(); if(errorMsg){
alert(errorMsg); return false;
}
}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

下面是所有的代碼如下:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var strategys = {
isNotEmpty: function(value,errorMsg) { if(value === '') { return errorMsg;
}
}, // 限制最小長度
minLength: function(value,length,errorMsg) { if(value.length < length) { return errorMsg;
}
}, // 手機(jī)號碼格式
mobileFormat: function(value,errorMsg) { if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg;
}
}
}; var Validator = function(){ this.cache = []; // 保存效驗規(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(); // 開始效驗 并取得效驗后的返回信息
if(msg) { return msg;
}
}
}; var validateFunc = function(){ var validator = new Validator(); // 創(chuàng)建一個Validator對象
/* 添加一些效驗規(guī)則 */ validator.add(registerForm.userName,'isNotEmpty','用戶名不能為空');
validator.add(registerForm.password,'minLength:6','密碼長度不能小于6位');
validator.add(registerForm.userName,'mobileFormat','手機(jī)號碼格式不正確'); var errorMsg = validator.start(); // 獲得效驗結(jié)果
return errorMsg; // 返回效驗結(jié)果
}; var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){ var errorMsg = validateFunc(); if(errorMsg){
alert(errorMsg); return false;
}
};</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

如上使用策略模式來編寫表單驗證代碼可以看到好處了,我們通過add配置的方式就完成了一個表單的效驗;這樣的話,那么代碼可以當(dāng)做一個組件來使用,并且可以隨時調(diào)用,在修改表單驗證規(guī)則的時候,也非常方便,通過傳遞參數(shù)即可調(diào)用;

給某個文本輸入框添加多種效驗規(guī)則

上面的代碼我們可以看到,我們只是給輸入框只能對應(yīng)一種效驗規(guī)則,比如上面的我們只能效驗輸入框是否為空,

validator.add(registerForm.userName,'isNotEmpty','用戶名不能為空');但是如果我們既要效驗輸入框是否為空,還要效驗輸入框的長度不要小于10位的話,那么我們期望需要像如下傳遞參數(shù):

validator.add(registerForm.userName,[{strategy:’isNotEmpty’,errorMsg:’用戶名不能為空’},{strategy: 'minLength:6',errorMsg:'用戶名長度不能小于6位'}])

我們可以編寫代碼如下:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">// 策略對象
var strategys = {
isNotEmpty: function(value,errorMsg) { if(value === '') { return errorMsg;
}
}, // 限制最小長度
minLength: function(value,length,errorMsg) { if(value.length < length) { return errorMsg;
}
}, // 手機(jī)號碼格式
mobileFormat: function(value,errorMsg) { if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg;
}
}
}; var Validator = function(){ this.cache = []; // 保存效驗規(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(); // 開始效驗 并取得效驗后的返回信息
if(msg) { return msg;
}
}
}; // 代碼調(diào)用
var registerForm = document.getElementById("registerForm"); var validateFunc = function(){ var validator = new Validator(); // 創(chuàng)建一個Validator對象
/* 添加一些效驗規(guī)則 */ validator.add(registerForm.userName,[
{strategy: 'isNotEmpty',errorMsg:'用戶名不能為空'},
{strategy: 'minLength:6',errorMsg:'用戶名長度不能小于6位'}
]);
validator.add(registerForm.password,[
{strategy: 'minLength:6',errorMsg:'密碼長度不能小于6位'},
]);
validator.add(registerForm.phoneNumber,[
{strategy: 'mobileFormat',errorMsg:'手機(jī)號格式不正確'},
]); var errorMsg = validator.start(); // 獲得效驗結(jié)果
return errorMsg; // 返回效驗結(jié)果
}; // 點(diǎn)擊確定提交
registerForm.onsubmit = function(){ var errorMsg = validateFunc(); if(errorMsg){
alert(errorMsg); return false;
}
}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

注意:如上代碼都是按照書上來做的,都是看到書的代碼,最主要我們理解策略模式實(shí)現(xiàn),比如上面的表單驗證功能是這樣封裝的代碼,我們平時使用jquery插件表單驗證代碼原來是這樣封裝的,為此我們以后也可以使用這種方式來封裝表單等學(xué)習(xí);

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實(shí)現(xiàn)同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 8,130評論 2 17
  • 單例模式 適用場景:可能會在場景中使用到對象,但只有一個實(shí)例,加載時并不主動創(chuàng)建,需要時才創(chuàng)建 最常見的單例模式,...
    Obeing閱讀 2,315評論 1 10
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,706評論 18 399
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,560評論 0 13
  • 自從我有記憶開始 你陪伴我的時間就很少 不是因為你不愛我 而是落在你身上的都是重活 白天,你在烈日下勞作 夜晚,你...
    執(zhí)著英閱讀 402評論 0 0

友情鏈接更多精彩內(nèi)容