JS設計模式-策略模式
前情提要
面向過程:將項目分解成很多步驟,為每個步驟編寫代碼。可維護性差。
面向對象:將項目分解成一個一個方法,然后根據每個步驟的需要調用方法。可維護性好。
面向對象的SOLID原則
-
單一職責:一個類有且只有一個職責
-
開放封閉:軟件實體(類,模塊,函數等)應該對擴展開放,對修改關閉
-
里氏替換:子類型必須能夠替換它們的基類
-
接口分離:接口只應該包括必要的方法而不是所有的
-
依賴倒置:高層次的模塊不應該依賴于低層次的模塊,而是都應該依賴于抽象
設計模式是啥?
設計模式是一套經驗,是對于某一類特定問題的簡潔而優雅的解決方案
-
代碼會具有更好的可維護性
-
學習設計模式,幫助我們更好的理解與運用SOLID
設計模式-策略模式
應用場景:當需要根據不同情景從多種算法中選擇一種執行時
重點思想:將變與不變分離
具體實現:將 不變的 算法封裝在 策略類 中, 策略類 只負責算法,傳入參數傳出參數; 變化的 是客戶端的請求,放在 情景類 中, 情景類 負責接收客戶端的請求并委托給 策略類
舉例1:會員折扣價格計算
需求:對 A 類型會員提供 20% 的促銷折扣,對 B 類型會員提供 10% 的促銷折扣,對 C 類型會員沒有折扣
首先我們定義會員折扣
// 定義不同會員的折扣
const discountA = 0.8,
discountB = 0.9,
discountC = 1;
最初的實現↓
// 計算折扣價格函數
function calculatePrice(type, totalPrice) {
if(type === 'A') {
return totalPrice*discountA;
}
if(type === 'B') {
return totalPrice*discountB;
}
if(type === 'C') {
return totalPrice*discountC;
}
}
// 調用函數
console.log(calculatePrice('A',100));
分析:算法是不變的,變的是會員類型和總價
運用策略模式↓
// 定義策略對象
const strategies = {
A: function(totalPrice) {
return totalPrice*discountA;
},
B: function(totalPrice) {
return totalPrice*discountB;
},
C: function(totalPrice) {
return totalPrice*discountC;
},
}
// 定義情景類
function calculatePrice(type, totalPrice) {
return strategies[type](totalPrice);
}
// 調用函數計算折扣價
console.log(calculatePrice('A',100));
策略對象還可以進一步抽象。
// 定義策略對象
const strategies = {
A: discountA,
B: discountB,
C: discountC,
}
// 定義情景類
function calculatePrice(type, totalPrice) {
return strategies[type]*(totalPrice);
}
// 調用函數計算折扣價
console.log(calculatePrice('A',100));
舉例2:注冊頁面表單驗證
需求: 用戶名 不可為空, 密碼 不能小于6位, 手機號碼 格式正確
// 偽一組數據
const registerForm = {
userName: 'liuxiaocui',
password: 'lxc123456',
phone: '18628337983',
}
最初的實現↓
// 校驗函數
function validate(data) {
if(data.userName === '') {
console.log('用戶名不能為空');
return;
}
if (data.password.length < 6) {
console.log('密碼長度不能小于6位');
return;
}
if (!/(^1[3|5|8][0-9]{9}$)/.test(data.phone)) {
console.log('手機號碼格式不正確');
return;
}
return true;
}
// 提交表單函數
function onSubmit(data) {
if(validate(data)) {
console.log('通過校驗');
// 別的操作blabla
}
}
// 調用提交表單函數
onSubmit(registerForm);
分析:校驗算法是不變的,變的是數據和校驗要求
運用策略模式↓
首先定義策略對象,注意每個函數的傳參。所有參數應該外部傳入。
// 定義策略對象
const 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;
}
}
}
回顧下需求。我們希望能方便得對數據進行校驗,也很可能一個數據需要多條校驗規則。
先來看下校驗函數是怎么寫的。
// 校驗函數
function validate(data) {
const validator = new Validator();
validator.add(data.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用戶名不能為空'
}, {
strategy: 'minLength:10',
errorMsg: '用戶名長度不能小于10位'
}]);
validator.add(data.password, [{
strategy: 'minLength:6',
errorMsg: '密碼長度不能小于6位'
}]);
validator.add(data.phone, [{
strategy: 'isMobile',
errorMsg: '手機號碼格式不正確'
}]);
return validator.start();
}
// 提交表單函數
function onSubmit(data) {
const errorMsg = validate(data);
if(errorMsg) {
console.log(errorMsg);
} else {
console.log('通過校驗');
// 別的操作blabla
}
}
// 調用函數
onSubmit(registerForm);
下面是情景類。
// 定義情景類
var Validator = function() {
// 存下需要進行的校驗
this.cache = [];
}
// 增加需要進行的校驗
Validator.prototype.add = function(data, rules) {
rules.forEach( item => {
// strategyAry的第一個元素為strategy名字,第二個值(如果有)為限制值
const strategyAry = item.strategy.split(':');
const errorMsg = item.errorMsg;
this.cache.push(function() {
const strategy = strategyAry.shift();
// unshift() 方法可向數組的開頭添加一個或更多元素,并返回新的長度。
// 把要校驗的數據放在數組第一位
strategyAry.unshift(data);
// 把錯誤信息放在數組最后一位
strategyAry.push(errorMsg);
// return strategies[strategy](...strategyAry);
return strategies[strategy].apply(null,strategyAry);
})
})
}
// 開始校驗
Validator.prototype.start = function() {
// 挨個運行this.cache中的校驗函數
for(let validatorFunc of this.cache) {
const errorMsg = validatorFunc();
if(errorMsg) {
return errorMsg;
}
}
}
小結
策略模式優點:將不變的算法單獨封裝在 策略類 中,通過 情景類 來接收請求并委托給 策略類 ,代碼結構清晰,使用方便
策略模式缺點:使用之前需要知道所有的策略類函數
來自:https://segmentfault.com/a/1190000008773507