JS设计模式初识(二)-策略模式

285 阅读3分钟

一、定义

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。俗话说,条条大路通罗马。在程序设计中,我们也常常遇到类似的情况,要实现某一个功能有多种方案可以选择。比如一个压缩文件的程序,既可以选择zip算法,也可以选择 gzip 算法。

以公司发年终奖为例:很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。例如,绩效为 S 的人年 终奖有 4 倍工资,绩效为 A的人年终奖有3倍工资,而绩效为 B 的人年终奖是 2 倍工资。假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖。

2.1 普通实现(bad)

    // 普通实现
    function calcBonus({level, salary}) {
        if (level === 'S') {
            return salary * level * 4;
        }
        if (level === 'A') {
            return salary * level * 3;
        }
        if (level === 'B') {
            return salary * level * 2;
        }
    }

这个函数的缺点, 1. 如果情况复杂的话可能会有多个if else; 2. 缺乏弹性, 如果需要 增加一个新的level C 并且把S的level调成5 需要深入代码逻辑, 才能修改, 3. 代码无法复用

2.2 使用策略模式改进

    const strategies = {
        'S': (salary) => salary * 4,
        'A': (salary) => salary * 3,
        'B': (salary) => salay * 2,
    }

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

    const t1 = calculateBonus({ level: 'S', salary: 20000 });
    console.log('t1 => ',t1);

通过使用策略模式重构代码,我们消除了原程序中大片的条件分支语句。所有跟计算奖金有 关的逻辑不再放在 Context 中,而是分布在各个策略对象中。Context 并没有计算奖金的能力,而是把这个职责委托给了某个策略对象。每个策略对象负责的算法已被各自封装在对象内部。当我们对这些策略对象发出“计算奖金”的请求时,它们会返回各自不同的计算结果,这正是对象多态性的体现,也是“它们可以相互替换”的目的。替换 Context 中当前保存的策略对象,便能执行不同的算法来得到我们想要的结果。

2.3 策略模式的应用(以表单校验为例)

// 数据结构
[{
    strategy: 'minLength:6',
    errorMsg: '用户名长度不能小于 10 位',
}]

基本代码实现

    import isString from 'lodash/isString';
    // 策略对象
    const strategies = {
        isNonEmpty: (value, errMsg) => {
            if (value === '') {
                return errMsg;
            }
        },
        minLength: (value, length, errMsg) => {
            if (val.length < length) {
                return errMsg;
            }
        },
        isMobil: (value, errMsg) => {
            var pattern = /^1[3|5|8][0-9]{9}$/ig;
            if (!pattern.text(value)) {
                return errMsg;
            }
        },
    };
    
    // 校验器对象
    function Validator() {
        this.cache = []; // 保存校验的函数
    }
    
    Validator.prototype.add = function(dom, rules) {
        const self = this;
        for(let i=0; i<rules.length; i++) {
            const rule = rules[i];
            const { strategy, errorMsg } = rule;
            const strategyArr = isString(strategy) && strategy.spilt(':');
            
            self.cache.push((() => {
                const strategy = strategyArr.shift();
                strategyArr.unshift(dom.value);
                strategyArr.push(errorMsg);
                return strategies[strategy].call(dom, ...strategyArr);
            }))

        }
    }

    Validator.prototype.valid = function() {
        let errorMsg;
        this.cache.every((item, index) => {
            const result = item();
            if (result) {
                errorMsg = result;
                return false;
            }
        });
        return errorMsg;
    }

客户端调用方式

    // 客户端调用方式
    const userInput = document.getElementById('userInput');
    const form = document.getElementById('form');
    function validForm() {
        const v1 = new Validator();
        v1.add( userInput, [
            { 
                strategy: 'isNonEmpty',
                errorMsg: '用户名不能为空'
            }, {
                strategy: 'minLength:6',
                errorMsg: '用户名长度不能小于 10 位'
            }]
        );
        const errorMsg = v1.valid();
        return errorMsg;
    }
    

    form.onsubmit = function() {
        const errorMsg = validForm();
        if (errorMsg) {
            alert(errorMsg);
            return false;
        }
    }

2.4 代码改进

    // 校验器对象代码改进
    class Validator {
        constructor() {
            this.cache = []; // 保存校验的函数
        }
        add = (dom, rules) => {
            for(let i=0; i<rules.length; i++) {
                const rule = rules[i];
                const { strategy, errorMsg } = rule;
                const strategyArr = isString(strategy) && strategy.spilt(':');
                
                this.cache.push((() => {
                    const strategy = strategyArr.shift();
                    strategyArr.unshift(dom.value);
                    strategyArr.push(errorMsg);
                    return strategies[strategy].call(dom, ...strategyArr);
                }))

            }
        }
        valid = () => {
            let errorMsg;
            this.cache.every((item, index) => {
                const result = item();
                if (result) {
                    errorMsg = result;
                    return false;
                }
            });
            return errorMsg;
        }
    };

二、总结

策略模式是一种常用且有效的设计模式,也具有优点和缺点

优点
  • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
  • 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。
  • 可复用
缺点
  • 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。
  • 会增加策略类或者策略对象