js 策略模式

来源:互联网 发布:网站信息采集到数据库 编辑:程序博客网 时间:2024/06/18 15:06
 js 策略模式 例如,很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的.例如,绩效为s的人年终奖有4倍工资, 绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资. 最初思路编写一个calculateBonus的函数来计算每个人的奖金数额.这个函数,接受两个参数:员工的工资数额和他的绩效考核等级.
    var calculateBonus = function(performanceLevel,salary){        if(performanceLevel === 'S'){            return salary * 4;        }        if(performanceLevel === 'A'){            return salary * 3;        }        if(performanceLevel === 'B'){            return salary * 2;        }    }    calculateBonus('B',20000);    calculateBonus('S',10000);
缺点:calculateBonus函数比较庞大,包含了很多的if-else语句,这些语句需要覆盖所有的逻辑分支 calculateBonus函数缺乏弹性,如果再增加一种新的绩效考核,我们必须修改函数内部实现. 这违反了开放-封闭原则 算法的复用性差,如果在程序的其它地方需要重用这些计算奖金的算法,我们的复制过去;
使用组合函数重构代码一般最容易想到的办法就是使用组合函数来重构代码,我们把各种算法封装到一个个的小函数里面,这些小函数有良好的命名,可以一目了然地知道它对应哪种算法,它们可以复用到程序的其它地方:
    var performanceS = function(salary){        return salary * 4;    }    var performanceA = function(salary){        return salary * 3;    }    var performanceB = function(salary){        return salary * 2;    }    var calculateBons = function(performanceLevel,salary){        if(performanceLevel === 'S'){            return performanceS(salary)        }        if(performanceLevel === 'A'){            return performanceA(salary)        }        if(performanceLevel === 'B'){            return performanceB(salary)        }    }    console.log(calculateBons('A',10000));
目前,虽然程序上得到一定程度上的改善,但是这种改善风格有限,问题仍然存在: calculateBons 函数有可能越来越大,而且在系统变化的时候缺乏弹性.使用策略模式重构代码策略模式指的是定义一系列的算法,把它们封装起来.目的就是将算法的使用与算法的实现分离出来一个基于策略模式的程序至少由两部分组成.第一部分是一组策略类,封装了具体的算法,并负责具体的计算过程;第二部分是环境类Context,Context接受用户的请求,随后把请求委托给某一个策略类.
    var performanceS = function(){};    performanceS.prototype.calculate = function(salary){        return salary * 4;    }    var performanceA = function(){};    performanceA.prototype.calculate = function(salary){        return salary * 3;    }    var performanceB = function(){};    performanceB.prototype.calculate = function(salary){        return salary * 2;    }    var Bonus = function(){        this.salary = null;//原始工资        this.strategy = null;//绩效等级对应的策略对象    }    Bonus.prototype.setSalary = function(salary){        this.salary = salary;// 设置员工的原始工资    }    Bonus.prototype.setStrategy = function(strategy){        this.strategy = strategy;//设置员工绩效等级对应的策略对象;    }    Bonus.prototype.getBonus = function(){// 取得奖金数额        return this.strategy.calculate(this.salary);//把计算奖金的操作委托给对应的策略对象    }    var bouns = new Bonus();    bouns.setSalary(10000);    bouns.setStrategy(new performanceS());//设置策略对象    console.log(bouns.getBonus());

我们让strategy对象从各个策略类中创建而来,这是模拟一些传统面向对象实现.实际上,在js中,函数也是对象,所以更简单和直接的做法是把strategy直接定义为函数;
    var strategies = {        'S':function(salary){            return salary * 4;        },        'A':function(salary){            return salary * 3;        },        'B':function(salary){            return salary * 2;        }    };    /*    同样,Context也没有必要用Bonus类来表示,我们已然用calculateBonus函数充当Context来接受用户的请求    * */    var calculateBonus = function(level,salary){        return strategies[level](salary);    };    console.log(calculateBonus('S',20000));


接下来,我们用策略模式重构表单验证
    var $btn = document.getElementsByTagName('button')[0],        $registerForm = document.getElementById('registerForm');    $btn.onclick = function(){        if($registerForm.userName.value === ''){            alert('用户名不能为空');            return false;        }        if($registerForm.password.value.length < 6){            alert('密码长度不能少于6位');            return false;        }        if(!/(^1[3|5|8][0-9]{9}$)/.test( $registerForm.phoneNumber.value ) ){            alert('手机号码格式不正确');            return false;        }    }
这是一种常见的代码编写方式,缺点如下"$btn.onclick函数比较庞大,包含了很多的if-else语句,这些语句覆盖了所有的校验规则$btn.onclick函数缺乏弹性,如果增加了一种新的校验规则,或者想把密码的长度校验从6改成8,都必须深入函数内部去修改,这是违反了开放-封闭原则;算法的复用性差,如果在程序中增加了另外一个表单,得复制过去;
用策略模式重构表单校验很显然第一部要把这些校验逻辑都封装成策略对象:
    var strategies = {        isNonEmpty:function(value,errorMsg){// 不为空            if(value === ''){                return errorMsg;            }        },        minLength:function(value,length,errorMsg){// 限制最小长度            if(value.length < length){                return errorMsg;            }        },        isMobile:function(value,errorMsg){// 手机号码格式            if(!/(^1[3|5|8][0-9]{9}$)/.test( value )){                return errorMsg;            }        }    }
接下来我们实现Validator类,Validator类在这里我们作为Context,负责接受用户的请求并委托给strategy对象.在给出Validator类的代码前,有必要提前了解用户是如何向Validator类发送请求的,有助于我们知道如何去编写Validator类的代码
    var validataFunc = function(){        var validator = new Validator();//创建一个validator对象        /**添加一些校验规则**/        validator.add(document.getElementById('userName'),'isNonEmpty','用户名不能为空');        validator.add(document.getElementById('password'),'minLength:10','密码长度不能少于10位');        validator.add(document.getElementById('phoneNumber'),'isMobile','手机号码格式不正确');        var errorMsg = validator.start();//获得校验结果        return errorMsg;    };    $btn.onclick = function(){        var errorMsg = validataFunc();// 如何errorMsg有确切的返回值,说明未通过校验        if(errorMsg){            alert(errorMsg);            return false;//阻止表单提交        }    };

可以看到,我们先创建了一个validator对象,然后通过validator.add方法,往validator对象中添加一些校验规则,validator.add方法接受3个参数,'minLength:6'是一个以冒号隔开的字符串;当往validator对象里添加完一系列的校验规则之后,会调用validator.start()方法来启动校验.如果validator.start()返回一个确切的errorMsg字符串当作返回值,说明该校验没通过,此时阻止表单提交最后就是Validator类的实现
    var Validator = function(){        this.cache = []; //保存校验规则    };    Validator.prototype.add = function( dom, rule, errorMsg ) {        var ary = rule.split(':'); //把strategy和参数分开        this.cache.push(function () { //把校验的步骤用空函数包装起来,并且放入cache            var strategy = ary.shift();// 用户挑选的strategy            ary.unshift(dom.value);//把input的value添加进参数列表            ary.push(errorMsg); //把errorMsg添加参数列表            return strategies[strategy].apply(dom, ary);        });    };    Validator.prototype.start = function(){        console.log(this.cache);        for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){            var msg = validatorFunc();// 开始校验,并取得校验后的返回信息            if ( msg ){// 如果有确切的返回值,说明校验没有通过;                return msg;            }        }    };