美文网首页
JavaScript 策略模式

JavaScript 策略模式

作者: kim_jin | 来源:发表于2018-10-31 16:38 被阅读0次

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

    策略模式计算奖金

    奖金发放的场景:绩效为S的人年终奖为4倍工资,绩效为A的年终奖为3倍工资,绩效为B的年终奖为2倍工资,那我们的编程的代码。

    最初代码的实现

    var calculateBouns = function(performanceLevel, salary){
    
      if(performanceLevel === 'S'){
        return salary * 4;
      }
    
      if(performanceLevel === 'A'){
        return salary*3;
      }
    
      if(performanceLevel === 'B'){
        return salary*2;
      }
    };
    
    calculateBouns ('B', 2000); // 4000
    calculateBouns ('S', 6000); // 24000
    

    现在的这段代码十分简单,但是存在很多的显而易见的问题。

    • 如果有很多的等级的话,就会有很多的if-else的语句,这样的语句需要覆盖整个的逻辑分支。
    • 该函数缺乏弹性,如果增加一个等级C的话,或是将S的系数进行更改的话,必须要进入到函数的内部进行实现,违反了开放-封闭原则。
    • 算法的复用性很差,如果在其他的地方进行重复的逻辑,我们只能进行复制粘贴,不能直接进行代码的复用。

    由于上面的代码存在很大的问题,所以我们决定对代码进行重构。

    使用组合函数进行代码的重构
    我们最容易想到的办法是通过组合函数进行代码的重构,,我们把各种算法封装到一个小的函数中,这些小的函数都有良好的命名,可以一目了然的知道用到了什么算法。还是上面的使用场景,进行代码的优化:

    var performanceLevelS = function(){
      return salary*4;
    }
    var performanceLevelA = function(){
      return salary*3;
    }
    var performanceLevelB = function(){
      return salary*2;
    }
    
    var calculateBouns = function(performanceLevel, salary){
    
      if(performanceLevel === 'S'){
        return performanceLevelS;
      }
    
      if(performanceLevel === 'A'){
        return performanceLevelA;
      }
    
      if(performanceLevel === 'B'){
        return performanceLevelB;
      }
    };
    
    calculateBouns ('B', 2000); // 4000
    

    上面的写法我们在一定的程度上解决了一定的问题,但是这样的改善是十分的有限的,当我们的逻辑很复杂的时候,calculateBouns 函数可能越来越大,而且系统的变化的时候,弹性有限。

    使用策略模式重构代码
    我们想到可以使用策略模式进行重构代码,策略模式的意义就是:定义一系列的算法,并将这些算法封装起来,把不变的部分和变化的部分分割开来是每一个设计模式的主题,策略模式也是不例外的,策略模式的目的就是将算法的使用和实现分割开来。
    在例子中,我们注意到了,算法使用的方式是不变的,都是根据某个算法取得计算后的的奖金的金额,而且算法的实现是各异和变化的,不同的绩效对应着不同的计算公式。
    一个基于策略模式的程序至少要有两个部分,第一部分是策略类:封装了具体的算法,并且负责具体计算的部分;第二部分是环境类context,用来接收客户的请求,然后将请求委托给某一个策略类。所以context中要维系对于某个策略对象的引用。
    首先使用策略类模仿传统的面向对象的实现,将绩效的计算规则封装到对应的策略类里面

    var performanceLevelS = function(){};
    
    performanceLevelS.prototype.calculate = function(salary){
      return salary*4;
    }
    
    var performanceLevelA = function(){};
    
    performanceLevelA.prototype.calculate = function(salary){
      return salary*3;
    }
    
    var performanceLevelB = function(){};
    
    performanceLevelB.prototype.calculate = function(salary){
      return salary*2;
    }
    

    定义一下奖金类:

    var Bonus = function(){
      this.strategy = null;
      this.salary = 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);
    }
    

    我们在看一下策略模式的思想:定义一系列的算法,把它们封装起来,并且使他们可以相互进行替换。我们再暂看说一下:定义一系列的算法,把它们各自封装成策略类,算法封装在策略类内部的方法里,在客户端对context发起请求,Context总是把请求委托给这些策略对象中间的某一个进行计算。
    至于如何完成剩下的代码,我们首先先创建一个bonus对象,并且给bonus对象设置一些原始的数据,然后呢,把某个计算奖金的策略对象也传入到bonus对象内部并进行保存,当调用bonus.getBonus的时候,bonus对象本身并没有能力去计算,而是把请求委托给之前保存好的策略对象。

    var  bonus = new Bonus();
    
    bonus.setSalary(1000); // 设置初始工资
    bonus.setStrategy (new performanceLevelA ()); //设置策略
    console.log(bonus.getBonus());
    

    针对JavaScript版本的策略模式
    上面的代码我们是模仿在传统的面向对象语言来进行实现的,实际上在JavaScript语言中,函数也是对象,所以最简单最直接的方法是把strategy指定定义为函数。

    var strategis = {
      "S":function(salary){
        return salary * 4;
      }
      "A":function(salary){
          return salary * 3;
        }
      "B":function(salary){
          return salary * 2;
        }
    };
    

    同样,context也没有必要必须用Bonus类来标识,我们依然使用calculateBonus函数充当context来接受用户的请求,经过改造,代码的结结构会更加简洁。

    var strategis = {
      "S":function(salary){
        return salary * 4;
      }
      "A":function(salary){
          return salary * 3;
        }
      "B":function(salary){
          return salary * 2;
        }
    };
    var calculateBonus = function(level, salary){  
    
    console.log(calculateBonus('S'),20000) // 80000
    }
    

    我们通过策略模式来进行代码的重构,我们消除了原程序中大片的条件分支语句。多有的有关计算的逻辑不放在context中,而是分布在策略对象中,context没有计算的能力,而是将这个职责委托给了某个策略对象。每一个策略对象负责的算法被封装在各自的对象内部,有一点向传统的多态,因此各个策略对象彼此之前是可以互换的。
    表单验证
    我们假设写一个注册的页面,在点击注册按钮之前,我们添加几条校验的逻辑。

    • 用户名不能为空
    • 密码长度不能小于6位
    • 手机号码必须符合格式

    当我们还没有引入策略模式的时候,我们的代码是下面这样的:

    <form id = 'registerForm' method = 'post'>
        请输入用户名:<input type="text" name="userName"/>
        请输入密码:<input type="text" name="password" />
        请输入手机号:<input type="text" name="phoneNumber" />
        <button>提交</button>
    </form>
    
    var registerForm = document.getElementById('registerForm');
        registerForm.onsubmit = function(){
          if(registerForm.userName.value === ''){
          alert('用户名不为空');
          return false;
        }
        if(registerForm.password.value.length < 6){
            alert('密码长度不能小于6位');
            return false;
        }
        if(!/(^1[3|5|8]{9}$)/.test(registerForm.phoneNumber.value) ){
            alert('手机号码格式不正确');
            return false;   
        }
    }
    

    这是最简单最常见的编码方式,但是它的缺点和计算奖金最开始的模板是一样的

    • registerForm.onsubmit 函数比较庞大,尤其是我们需要校验的表格比较负责的时候,包含了很多的if-else语句,这些语句需要覆盖现在所有的校验规则
    • registerForm.onsubmit 函数缺乏弹性,如果增加新的校验规则,或者是想要将6为密码修改为8位密码,我们必须深入到函数的内部进行实现,违反了开闭原则
    • 算法的复用性很差,如果还有一个表单需要校验,我们几乎要将这个逻辑完全的复制。

    策略模式的表单验证

    var strategies = {
        isNotEmpty: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]{9}$)/.test(value)){
                return errorMsg;
            }
        }
    }
    

    将各个算法进行简单的封装,为后续的调用提供方便,现在我们准备实现validator类作为context,负责接收用户的请求并委托给strategy对象。

    var validataFunc = function(){
        var validator = new Validator();
            
        //添加一些校验规则
        validator.add(registerForm.userName, 'isNotEmpty', '用户名不为空');
        validator.add(registerForm.password, 'minLength:6', '密码长度不能小于6位');
        validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确');
        var errorMsg = validator.start(); //获得校验的结果
        return errorMsg; //返回校验的结果
    }
    var registerForm = document.getElementById('registerForm');
    registerForm.onsubmit = function(){ //如果errorMsg有返回值,说明没有通过校验
        if(errorMsg){
            alert(errorMsg);
            return false;//阻止表单的提交
        }
    }
    

    在代码中,我们先创建一个validator对象,然后通过validator.add方法向validator对象中添加一些校验规则。validator.add方法接受3个参数,用下面的这段来吗来进行说明。validator.add(registerForm.password, 'minLength:6', '密码长度不能小于6位');

    • registerForm.password为参加校验的input的输入框
    • minLength:6用冒号进行分隔的字符串,冒号前是算法的名字,冒号后面是我们要传入的参数,如果不需要额外的信息,就不用添加冒号,直接传入算法的名字
    • 第三个参数:当校验未通过的时候返回的错误信息

    当我们向validator对象里添加完一系列的的校验规则之后,会调用validator.start()方法来启动校验,当该方法返回一个确切的errorMsg的值的时候,这个时候onsubmit方法会返回false来阻止表单的提交。让我们最后来构建Validator类的实现

    var Validator = function (){
        this.cache = []; //检验校验的规则
    }
    Validator.prototype.add = function(dom, rule, erroMsg){
        for(var i = 0, validataFunc; validataFunc = this.cache[i++]){
            var msg = validataFunc();
            if(msg){
                return msg;
            }
        }
    }
    

    我们以后想要加入规则,只需要配置一些简单的表单验证,这些规则也可以简单的复用到程序的任何地方,甚至可以以插件的形式移植到其他的项目中去。假设我们想要修改密码长度不可以小于10位,我们可以这么进行调用validator.add(registerForm.password, 'minLength:10', '密码长度不能小于10位');
    给某一个文本框添加多种校验规则
    如果我们想给某一个文本框添加多种校验规则的话,比如说:我们要校验输入的用户名不为空且长度不小于10位,我们怎么进行校验呢?

    validator.add(registerForm.userName, [
    {
    strategy:'isNotEmpty',
    errorMsg:'用户名不能为空'
    }, {
    strategy:'minLength:10',
    errorMsg:'用户名长度不能小于10位'
    }
    ]);
    
    //策略对象
    var strategies = {
        isNotEmpty: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]{9}$)/.test(value)){
                return errorMsg;
            }
        }
    }
    var Validator = function (){
        this.cache = []; //检验校验的规则
    }
    Validator.prototype.add = function(dom, rules){
        var self = this;
        for(var i = 0, rule; rule = rules[i++]){
            (function(rule){
                var strategyAry = rule.strategy.split(':');
                var errorMsg = rule.errorMsg;
                self.cache.push(function(){
                    var strategy = strategyAry.shift();
                    strategyAry.unshift(dom.value);
                    strategyAry.push(errorMsg);
                    return strategies[strategy].apply(dom, strategyAry);
                });
            })(rule)
        }
    };
    Validator.prototype.start = function(){
        for(var i = 0, validataFunc; validataFunc = this.cache[i++]){
            var msg = validataFunc();
            if(msg){
                return msg;
            }
        }
    };      
    //客户端进行调用的时候
    var registerForm = document.getElementById('registerForm');
    var validataFunc = function(){
        var validator = new Validator();
                
        validator.add(registerForm.userName,[{
            strategy: 'isNotEmpty',
            errorMsg: '用户名不能为空'
        },
        {
            strategy: 'minLength:10',
            errorMsg: '用户名长度不能小于10'
        }]);
        validator.add(registerForm.password,[{
            strategy: 'minLength:6',
            errorMsg: '密码长度不小于6'
        }]);
        validator.add(registerForm.phoneNumber,[{
            strategy: 'isMobile',
            errorMsg: '手机号码格式不正确'
        }]);
        var errorMsg = validator.start();
        return errorMsg;
    }
    registerForm.onsubmit = function(){
                
        var errorMsg = validataFunc();
        if(errorMsg){
            alert(errorMsg);    
            return false;
        }
    }
    

    策略模式的优点:

    • 策略模式利用组合、委托和多态,可以避免多重条件选择
    • 可以完美的支持开放-关闭原则,将算法独立的封装在strategy中,是算法之间易于切换、易于理解、易于扩展。
    • 策略模式的算法也可以复用到系统的其他地方,从而避免许多的重复粘贴
    • 策略模式利用组合和委托Context拥有执行算法的能力,这也是继承的一种更加轻便的替代方案。

    相关文章

      网友评论

          本文标题:JavaScript 策略模式

          本文链接:https://www.haomeiwen.com/subject/jxffzftx.html