mhahaha/Javascript-Design-Patterns

javascript设计模式 - 策略模式

mhahaha opened this issue · 0 comments

俗话说,条条大路通罗马,完成一件事情,我们往往有很多选择,根据自身不同的条件我们可以有不同的选择方案。比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路。

  • 如果没有时间但是不在乎钱,可以选择坐飞机。
  • 如果没有钱,可以选择坐大巴或者火车。
  • 如果再穷一点,可以选择骑自行车。

这类选择过程也即是我们即将学习的策略模式。

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

示例:编写一个计算奖金(bonus)的算法,薪资(salary) 为最小单元

  • 级别(level)S bonus = salary * 3
  • 级别(level)A bonus = salary * 2
  • 级别(level)B bonus = salary * 1

可以看出,计算出员工奖金,我们需要知道员工的级别及薪资,我们可以编写一个方法,将级别和薪资当做参数传进去,这样我们就可以得到员工奖金。如下:

const calculateBonus = (performanceLevel, salary) => {
    if (performanceLevel === 'S') {
        return salary * 3
    }
    if (performanceLevel === 'A') {
        return salary * 2
    }
    if (performanceLevel === 'B') {
        return salary * 1
    }
}

calculateBonus('S', 8000)

可以发现,这段代码十分简单,但是存在着显而易见的缺点。

  • calculateBonus 函数比较庞大,包含了很多 if-else 语句,这些语句需要覆盖所有的逻辑
    分支。
  • calculateBonus 函数缺乏弹性,如果增加了一种新的绩效等级 C,或者想把绩效 S 的奖金
    系数改为 5,那我们必须深入 calculateBonus 函数的内部实现,这是违反开放-封闭原则的。
  • 算法的复用性差,如果在程序的其他地方需要重用这些计算奖金的算法呢?我们的选择
    只有复制和粘贴。

在javascript中,声明一个对象的成本是很低的,我们根据策略模式的定义来改造下,上面计算函数我们完全可以简化成一个对象来进行维护,key是级别,value则是一个计算奖金的函数,代码如下:

const strategies = {
    'S': salary =>  salary * 3,
    'A': salary =>  salary * 2,
    'B': salary =>  salary * 1
}; 

const calculateBonus = (level, salary) => strategies[ level ]( salary )

calculateBonus( 'S', 20000 )

这样一来,我们消除了大片的if-esle条件分支语句,使实现过程更加简洁,只需维护好strategies这个策略对象即可。

以上示例我们可以了解到策略模式的基本使用,那在我们实际开发过程,其实很多地方都可以用策略模式来进行改进,比如我们前端的一些校验,比较急躁的时候可能会出现上面庞大的if-else来进行各类验证条件的编写,那现在我们用策略模式来改进试试看,实现代码如下:

// 策略对象
const strategies = {
    isNonEmpty: (value, errorMsg) => {
        if (value === '') {
            return errorMsg
        }
    },
    minLength: (value, length, errorMsg) => {
        if (value.length < length) {
            return errorMsg
        }
    },
    isMobile: (value, errorMsg) => {
        if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg
        }
    }
}

// 处理校验的校验类
class Validator {
    constructor() {
        this.cache = []
    }

    add (ele, rules) {
        for (let i = 0, rule; (rule = rules[i++]);) {
            const strategyAry = rule.strategy.split(':')
            const errorMsg = rule.errorMsg
            this.cache.push(() => {
                const strategy = strategyAry.shift()
                strategyAry.unshift(ele.value)
                strategyAry.push(errorMsg)
                return strategies[strategy].apply(ele, strategyAry)
            })
        }
    }

    start () {
        for (let i = 0, validatorFunc; (validatorFunc = this.cache[i++]);) {
            const errorMsg = validatorFunc()
            if (errorMsg) {
                return errorMsg
            }
        }
    }
}

// demo
const registerForm = {
    userName: {
        value: 'lalalalala'
    },
    password: {
        value: 'mimamima'
    },
    phoneNumber: {
        value: 13123456789
    }
}

const validataFunc = () => {
    const validator = new Validator()
    validator.add(registerForm.userName, [
        {
            strategy: 'isNonEmpty',
            errorMsg: '用户名不能为空'
        },
        {
            strategy: 'minLength:6',
            errorMsg: '用户名长度不能小于 6 位'
        }
    ]);
    validator.add(registerForm.password, [
        {
            strategy: 'minLength:6',
            errorMsg: '密码长度不能小于 6 位'
        }
    ]);
    validator.add(registerForm.phoneNumber, [
        {
            strategy: 'isMobile',
            errorMsg: '手机号码格式不正确'
        }
    ])

    const errorMsg = validator.start() // 开始校验,并取得校验后的返回信息
    return errorMsg
}

const errorMsg = validataFunc()
if (errorMsg) {
    console.log(errorMsg)
}

这样我们按照策略模式新增并维护一个策略类,然后按照规则添加各数据项的校验就好,保证了功能代码可读性及扩展性。

总结

实际开发过程,当发现一个方法逐渐变得庞大,if-else写得让你难受的时候,试试策略模式改造能否解决痛点。