Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javascript设计模式 - 策略模式 #2

Open
mhahaha opened this issue Oct 29, 2018 · 0 comments
Open

javascript设计模式 - 策略模式 #2

mhahaha opened this issue Oct 29, 2018 · 0 comments

Comments

@mhahaha
Copy link
Owner

mhahaha commented Oct 29, 2018

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

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

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

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

示例:编写一个计算奖金(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写得让你难受的时候,试试策略模式改造能否解决痛点。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant