什麼時候用策略模式?
- 各判断条件下的策略相互独立且可复用
- 策略内部逻辑相对复杂
- 策略需要灵活组合
使用策略模式的優點:
- 1.使程式碼更加清晰可讀,減少繁多的if-else
- 2.功能更易拓展,可維護,健壯性
缺點
- 1.策略類會增多
- 2.業務邏輯分散到各個實現類中,而且沒有一個地方可以俯視整個業務邏輯
策略模式中,最常運用的即為表單驗證
表單裡的欄位數量非常有可能在日後增加,隨著欄位增加,也就代表著驗證的方式可能也增加,這是時候策略模式會是一個好方法
<form id="registerForm">
請輸入使用者名稱:<input type="text" name="userName" />
請輸入密碼:<input type="text" name="password" />
請輸入手機號碼:<input type="text" name="phoneNumber" />
<button type=button>提交</button>
</form>
/***********************策略物件**************************/
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 類**************************/
var Validator = function () {
this.cache = [];
};
Validator.prototype.add = function (dom, rules) {
var self = this;
for (let i = 0, rule; rule = rules[i++];) {
// 分離條件即屬性 如: minLength:6
var strategyAry = rule.strategy.split(':');
var errorMsg = rule.errorMsg;
self.cache.push(function () {
// 第一個為條件名稱 對應 strategies裡的key
var strategy = strategyAry.shift();
// 獲取表單輸入的內容
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
// 使用apply 將strategies[strategy]中的this 指向指定的欄位
return strategies[strategy].apply(dom, strategyAry);
})
}
};
Validator.prototype.start = function () {
/*
這裡條件表達式不同於以往的 <= xxx.length而使用validatorFunc,
這是因為這裡的遞增表達式 是使用this.cachee[i++]並賦值給validatorFunc
也就是說當this.cache[i++]不成立時,validatorFunc會變成undefined 則此時退出循環
*/
console.log(this.cache)
/*
this.cache 數組存放著strategies[strategy].apply(dom, strategyAry); 的函數
調用時則會將參數傳給strategies各自對應的方法,並驗證是否錯誤
*/
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:6',
errorMsg: '使用者名稱長度不能小於10 位'
}]);
// validator.add(registerForm.password, [{
// strategy: 'minLength:6',
// errorMsg: '密碼長度不能小於6 位'
// }]);
var errorMsg = validator.start();
console.log(errorMsg)
return errorMsg;
}
registerForm.onclick = function () {
var errorMsg = validataFunc();
if (errorMsg) {
// alert(errorMsg);
return false;
}
};
或者是說,在我的專案當中,有提供給商家設定的後台,而一個商家底下有很多管理者,這些管理者有不同的權限,前端則依據這些權限來執行對應的邏輯
由下方代碼可以發現到,如果後台角色在日後增加的話在promise裡面的代碼會變得越來越長,且if else 也是越來越長....
// 沒有策略模式的情況下
function showPart1() {
console.log(1)
}
function showPart2() {
console.log(2)
}
function showPart3() {
console.log(3)
}
axios.get('xxx').then(res => {
if (res == 'boss') {
showPart1()
showPart2()
showPart3()
} else if (res == 'manner') {
showPart1()
showPart2()
} else if (res == 'staff') {
showPart3()
}
})
使用策略模式後整體代碼就變得簡潔許多,日後如果要再增加角色,可以直接在power當中增加,且一目了然所有角色的邏輯代碼
// 用策略模式的情况
function showControl() {
this.status = ''
this.power = {
boss: function() {
showPart1()
showPart2()
showPart3()
},
manner: function() {
showPart1()
showPart2()
},
staff: function() {
showPart3()
}
}
}
showControl.prototype.show = function() {
var self = this
axios.get('xxx').then(res => {
self.status = res
self.power[self.status]()
})
}
new showControl().show()
再舉個栗子也是我業務中常常遇到的狀況,那就是多重嵌套条件地狱
隨著業務邏輯的擴展,後端那邊不斷發展出各種判斷方法,前端這邊也是各種判斷
這樣很容易就形成了多重嵌套条件的問題(if 裡面還有if 還有 if ....),很顯然的是,這種if判斷的情況條件不會停止只會不斷的增加。
如果使用一般方式(if -else)則會像是這樣:
const orderType = 1 // 1: 美妆,2:电器,3:家具
const orderWay = 1 // 1:h5,2:app,3:小程序
const orderMoney = 100 // 金额范围划分,0-100,100-1000,1000以上,跳转的订单详情也不相同
if (orderType === 1) {
if (orderWay === 1) {
if (0 <= orderMoney && orderMoney < 100) {
console.log('美妆订单h5-0')
} else if (orderMoney < 1000) {
console.log('美妆订单h5-100')
} else {
console.log('美妆订单h5-1000')
}
} else if (orderWay === 2) {
if (0 <= orderMoney && orderMoney < 100) {
console.log('美妆订单app-0')
} else if (orderMoney < 1000) {
console.log('美妆订单app-100')
} else {
console.log('美妆订单app-1000')
}
} else if (orderWay === 3) {
if (0 <= orderMoney && orderMoney < 100) {
console.log('美妆订单小程序-0')
} else if (orderMoney < 1000) {
console.log('美妆订单小程序-100')
} else {
console.log('美妆订单小程序-1000')
}
}
}
/*
作者:CookieBoty
链接:https://juejin.cn/post/6844904194575433735
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
這個時候策略模式就可以幫助我們優化代碼
const orderType = 1 // 1: 美妆,2:电器,3:家具
const orderWay = 1 // 1:h5,2:app,3:小程序
const orderMoney = 10000 // 金额范围划分,0-100,100-1000,1000以上,跳转的订单详情也不相同
const orderMoneyStrategy = (orderMoney) => { // 提取金额策略
if (0 <= orderMoney && orderMoney < 100) {
return 1
} else if (orderMoney < 1000) {
return 2
}
return 3
}
const strategy = () => { // 订单类型+环境类型策略
const map = new Map([
[{
orderType: 1,
orderWay: 1,
orderMoney: 1
}, () => {
console.log('美妆订单h5-0')
}],
[{
orderType: 1,
orderWay: 1,
orderMoney: 2
}, () => {
console.log('美妆订单h5-100')
}],
[{
orderType: 1,
orderWay: 1,
orderMoney: 3
}, () => {
console.log('美妆订单h5-1000')
}],
])
return map
}
const run = (orderType, orderWay, orderMoney) => {
let action = [...strategy()].filter(([key, value]) => (key.orderType === orderType && key.orderWay === orderWay && key.orderMoney === orderMoney))
action.forEach(([key, value]) => value.call(this))
}
run(orderType, orderWay, orderMoneyStrategy(orderMoney))
/*
作者:CookieBoty
链接:https://juejin.cn/post/6844904194575433735
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
最後再舉一個例子,也是我業務邏輯中碰到的,需求中需要製做自動化的智能客服,客戶點擊問題來獲得下一層的問題。其中最主要的問題是,要如何記住客戶都點過哪些問題呢? 因為返回的數據都和客戶的每一層選項關聯,這個時候我在每一層都埋下來每一個問題對應的key,也就是說每一個問題的key會對應著客戶第一次到這一次的紀錄,這個問題解決了但是有更麻煩的問題就是每點擊不同的問題則執行不同的邏輯,這邊我也用了策略模式來解決:
determineAction (data,stringData) {
const rules = [
{
matchRE: /^action=aaaa=bbbb$/,
action : function (data) {return { obj: action1}}
},
{
matchRE: /^action=aaaa=ccccc=ddddd$/,
action : function (data) {return { obj: action2, type: 'action2' }}
},
{
matchRE: /^action=xxxxx=yyyyyy=card$/,
action: function (data) {return { obj: action3, type: 'action3' }}
},
{
matchRE: /^action=mcNoResponse$/,
action : function (data) {return { obj: action4}}
}
]
}
// 將liff回傳的參數用正則匹配
for (let i = 0; i < rules.length; i++) {
if (rules[i].matchRE.test(stringData)) {
this.action = rules[i].action(data)
break
}
}
透過以上這些例子我們可以充分了解到策略模式的好處,可以使條件邏輯更加清晰,但是也不難發現到 上述例子也能形成策略模式在使用中不能減少很多的代碼量,策略越多,分解組合的過程會越複雜,所以的使用過程中要合理的表達。