JS中的策略模式

101 阅读2分钟

本文摘于《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({...})
}

使用策略模式重构代码后,我们仅仅通过“配置”的方式就可以完成表单的校验,这些规则也可以复用在别的任何地方,还能作为插件的形式,方便地被移植到其他项目中。