本文摘于《JS设计模式与开发实践》
策略模式有广泛的应用,首先以年终奖的计算为例进行介绍。假如,绩效为S的人年终奖是4倍工资,绩效为A的人年终奖是3倍工资,绩效为B的人年终奖是2倍工资。我们需要提供一段代码来计算员工的年终奖:
const calculateBonus = function(){
if(performance === 'S') {
return salary * 4
}
if(performance === 'A') {
return salary * 3
}
if(performance === 'B') {
return salary * 2
}
}
可以看出,这段代码非常简单,但存在很多缺点:
- 包含很多if-else语句,需要覆盖所有逻辑分支
- 函数缺乏弹性,如果新增一种绩效等级C或者将S等级的系数变为5,我们将不得不深入函数内部进行修改,这违反了开放-封闭原则
- 函数复用性差,如果我们在程序的其他地方需要重用这个算法,只能选择复制粘贴
接下来,尝试使用策略模式重构代码,一个基于策略模式的程序至少要由两部分组成。第一部分是策略类,封装具体的算法并负责具体的计算过程。第二部分是环境类,它接受客户的请求,随后将请求委托给某个策略类。我们直接使用JS的方式使用策略模式:
const stratigies = {
"S": function( salary ) {
return salary * 4
},
"A": function( salary ) {
return salary * 3
},
"B": function( salary ) {
return salary * 2
}
}
const calculateBonus = (level, salary) => {
return stratigies[level](salary)
}
可以看到使用策略模式重构代码后,代码变得很清晰,且可扩展性、复用性强。我们消除了大量的if-else语句,所有与计算相关的逻辑都分布在各个策略对象中,而不是环境类。
使用策略模式进行表单校验
假设我们正在编写一个注册页面,在点击注册按钮前,有如下几条校验逻辑:
- 用户名不能为空
- 密码长度不少于6位
- 手机号码必须符合格式
根据这三条规则,我们可以写出如下代码:
const submit = () => {
if(userName.value === '') {
alert('用户名不能为空')
return
}
if(password.value.length < 6) {
alert('密码长度不得少于6位')
return
}
if(!/(^1[3|5|8][0-9]{9}$)/.test(phoneNum.value)) {
alert('手机号码格式不正确')
return
}
axios.post({...})
}
这是一种很常见的编写方式,我自己之前也在写这样的代码,它的缺点跟计算奖金的初始版本一模一样。下面尝试使用策略模式重构上面的代码:
// 策略对象
const stratigies = {
isNonEmpty: function( value, errMsg ) {
if(value === '') return errMsg
},
minLength: function(value, length, errMsg ) {
if(value.length < length) return errMsg
},
isMobile: function(value, errMsg ) {
if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) return errMsg
}
}
// 环境类
class Validator {
cache = []
add(value, rule, errMsg){
const arr = rule.split(':') // 把strategy和参数分开,如:minLength:10 => ['minLength','10']
this.cache.push(() => {
const strategy = arr.shift() //
arr.unshift(value)
arr.push(errMsg)
return stratigies[ strategy ].apply(null, arr)
})
}
start(){
for(let validateFn of this.cache) {
const errMsg = validateFn()
if(errMsg) return errMsg
}
}
}
写好了策略对象和环境类,现在可以这样重构之前的表单校验:
const validateFunc = () => {
const validator = new Validator()
validator.add(userName.value, 'isNonEmpty','用户名不能为空')
validator.add(password.value, 'minLength:6','密码长度不能少于6位')
validator.add(phoneNum.value, 'isMobile', '手机号码格式不正确')
return validator.start()
}
const submit = () => {
const errMsg = validateFunc()
if(errMsg) {
alert(errMsg)
return
}
axios.post({...})
}
使用策略模式重构代码后,我们仅仅通过“配置”的方式就可以完成表单的校验,这些规则也可以复用在别的任何地方,还能作为插件的形式,方便地被移植到其他项目中。