场景
商家大促活动:根据不同种类的商品,分别有不同的折扣
| 商品种类 | 折扣 |
|---|---|
| A | 90% |
| B | 85% |
| C | 80% |
| D | 75% |
粗暴手段
function getSalePrice(type, originPrice) {
let salePrice = originPrice;
if (type === 'A') {
salePrice *= 0.9;
} else if (type === 'B') {
salePrice *= 0.85;
} else if (type === 'C') {
salePrice *= 0.8;
} else if (type === 'D') {
salePrice *= 0.75;
}
return salePrice;
}
对于上述场景来说,getSalePrice能够根据不同商品种类计算出折扣后的出售价格,在这种比较简单没那么复杂的场景下,已经满足使用了;但如果活动效果很好,商家决定临时增加多几个商品,那又得去修改getSalePrice函数
function getSalePrice(type, originPrice) {
let salePrice = originPrice;
if (type === 'A') {
salePrice *= 0.9;
} else if (type === 'B') {
salePrice *= 0.85;
} else if (type === 'C') {
salePrice *= 0.8;
} else if (type === 'D') {
salePrice *= 0.75;
} else if (type === 'E') {
salePrice *= 0.6;
} else if (type === 'F') {
salePrice *= 0.5;
} else if (type === 'G') {
salePrice *= 0.4;
}
return salePrice;
}
这样会导致 getSalePrice 越来越胖越来越臃肿,而且if-else条件判断太多,这非常不利于后续的维护,相信大家都不想别人接受自己的代码的时候,大吼一声“shit”,就算做不到“Wow”,也应该能达到“Great”的地步;
对于这种场景,策略模式就派上用场了,对于不同的商品,用对应不同的策略处理价格;
策略模式
// 价格策略
var priceStrategies = {
'A': function(originPrice) {
return originPrice *= 0.9;
},
'B': function(originPrice) {
return originPrice *= 0.85;
},
'C': function(originPrice) {
return originPrice *= 0.8;
},
'D': function(originPrice) {
return originPrice *= 0.75;
},
'E': function(originPrice) {
return originPrice *= 0.75;
},
'F': function(originPrice) {
return originPrice *= 0.75;
},
'G': function(originPrice) {
return originPrice *= 0.75;
},
// 更多商品种类策略
}
将对应的种类以及出售价格进行对象key-value映射,形成对应的不同商品的价格策略;
function getSalePrice(type, originPrice) {
return priceSrategies[type](originPrice);
}
这样整理之后,getSalePrice则不再继续处理商品价格的多少,只需交给priceStrategies这个价格策略去执行,返回具体商品的价格,也方便不同策略的扩展;同时测试再回归的时候,不需要对原有的商品进行回归,因为你修改的并不是getSalePrice函数,不影响原有逻辑👍.
实战
其实以上的场景是非常简单的,实际上这种场景非常理想,即使不用策略模式,也能写出“Great”代码;
假设场景如下: 表单验证,当前注册页面有
- 账户名
- 密码
- 手机号
这三项表单需要验证
未用策略模式
// html
<form id="registerForm">
请输入账户: <input type="text" name="account" id="account"/>
请输入密码: <input type="password" name="password" id="password"/>
请输入手机号: <input type="text" name="phoneNumber" id="phoneNumber"/>
</form>
<button id="submit">提交</button>
// js
const submit = document.querySelector('#submit');
const account = document.getElementById('account');
const password = document.getElementById('password');
const phone = document.getElementById('phone');
// 1、未使用策略模式
submit.addEventListener('click', function() {
if (account.value === '') {
alert('用户名不能为空');
return false;
}
if (password.value.length < 10) {
alert('密码长度不能小于10个字符');
return false;
}
if (!/^1[3-8][0-9]{9}$/.test(phoneNumber.value)) {
alert('手机号码格式不正确');
return false;
}
// do other things
});
同样的,如果对表单项进行改动,对于提交动作submit都要进行修改,更有甚者,如果多个页面,都需要类似的表单验证,类似这种写法,可能需要将这段代码进行CTRL-C和CTRL-V,当然也可以将各种验证函数抽出来,如:
function validatePhone(phone) {}
function validatePassword(password) {}
function validateAccount(account) {}
以上写法当然可以,但是没使用策略模式
使用策略模式
// 验证策略
var validateStrategies = {
isEmpty: function(value, errMsg) {
if (value === '') {
return errMsg;
}
},
isPhone: function(value, errMsg) {
if (!/^1[3-8][0-9]{9}$/.test(value)) {
return errMsg;
}
},
minLength: function(value, length, errMsg) {
if (value.length < Number(length)) {
return errMsg;
}
}
}
function Validator() {
this.validators = [];
}
Validator.prototype.register = function(dom, rules, errMsg) {
const params = rules.split(':');
this.validators.push(function() {
const rule = params.shift();
const value = dom.value;
params.unshift(value);
params.push(errMsg); // [value, length?, errMsg]
return validateStrategies[rule].apply(dom, params);
})
}
Validator.prototype.validate = function() {
for(var i = 0; i < this.validators.length; i++) {
const msg = this.validators[i]();
if (msg) {
return msg;
}
}
}
submit.addEventListener('click', function() {
const validator = new Validator();
validator.register(account, 'isEmpty', '用户名不能为空');
validator.register(password, 'minLength:10', '密码长度不能小于10个字符');
validator.register(phone, 'isPhone', '手机号码格式不正确');
const errMsg = validator.validate();
if (errMsg) alert(errMsg);
return false;
});
在这里通过策略模式: 将不同表单项的验证分为不同的策略
- 账户验证
- 密码验证
- 手机格式验证
同时通过Validator类,进行对当前所需的验证策略做对应的注册-register,在后续用到相关验证的时候,只需要在策略中新增,并在实际环境中注册,即可增加新的策略功能;方便业务扩展的同时,又使代码不会这么臃肿,Great👍
总结
总的来说,使用策略模式,能够将一些繁复且臃肿的代码,变得纤细且简洁,相信大家都不喜欢那种臃肿并且充斥着if-else的逻辑代码,在遇上这种场景,可以抽象出不同分支对应的策略,使主函数更加简洁,同时通过策略的方式,更容易扩展业务逻辑,并且减少代码改动导致的错误。
参考书籍
《JavaScript设计模式与开发实践》-曾探