js策略模式

本文介绍了策略模式的概念及其在计算奖金、缓动动画和表单验证等场景中的具体应用。通过重构示例代码,展示了如何利用策略模式提高程序的灵活性和可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

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

使用策略模式计算奖金

需求为绩效S的人4倍奖金,绩效为A的人3倍奖金,绩效为B的人年终奖是2倍工资。

  var caculateBonus = function(performanceLevel, salary){
      if(performanceLevel === 'S'){
          return salary * 4;
      }
      if(performanceLevel === 'A'){
          return salary * 3;
      }
      if(performanceLevel === 'B'){
          return salary * 2;
      }
  }

这段代码有一些缺点,如果我们想改变绩效的奖金系数,我们必须修改calculateBonus函数的内部实现,这是违反开放-封闭原则的。算法的复用性很差,如果在程序的其他地方需要重用这些计算奖金的方法,就只有复制和粘贴。

使用组合函数重构代码

我们把各种算法封装到一个个小函数里面,这些小函数有着良好的命名,可以一目了然知道它对应者哪种算法

   var performanceS = function(salary){
       return salary * 4;
   };
   var performanceA = function(salary){
       return salary * 3;
   };
   var performanceB = function(salary){
       return salary * 2;
   }
   var calculateBonus = function(performanceLevel, salary){
      if(performanceLevel === 'S'){
         return performanceS(salary);
      }
      //...
   }

这样依然没有解决我们的问题

使用策略模式重构代码

一个基于策略模式的程序至少有两部分组成,第一个部分是策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context,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;
   };

接下来定义奖金类Bonus

   var Bonus = function(){
       this.salary = null;  
       this.strategy = null; //绩效等级对应的策略对象
   }
   Bonus.prototype.setSalary = function(salary){
       this.salary = salary;
   }
   Bonus.prototype.setStrategy = function(salary){
       this.strategy = strategy;
   }
   Bonus.prototype.getBonus = function(){
       return this.strategy.calculate(this.salary);
   }

接下来

   var bonus = new Bonus();
   bonus.setSalary(10000);
   bonus.setStrategy(new performanceS());//设置策略对象
   console.log(bonus.getBonus());
   bonus.setStrategy();

js版本的策略模式

我们让strategy对象从各个策略类中创建而来,这时模拟一些传统的面向对象语言的实现,实际上在js中,函数也是对象,所以更简单的做法是把strategy直接定义成函数

   //[ˈstrætədʒi]策略
   var strategies = function(){
       '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));
   console.log(calculateBonus('A', 10000));

使用策略模式实现缓动动画

这些方法都接受4个参数,这4个参数的含义分别是动画已消耗的时间,小球原始位置,小球目标位置和动画持续的时间,返回的值则是动画元素应该处在的当前位置

   var tween = {
       linear: function(t, b, c, d){
           return c * t / d + b;
       },
       easeIn: function(t, b, c, d){
           return c * t / d + b;
       }       },
       strongEaseIn: function(t, b, c, d){
           return c * t / d + b;
       }
   }

现在进入代码实现阶段,首先在页面放置一个div

   <body>
       <div style="position:absolute;background:blue" id="div"></div>
   </body>

接下来定义Animate类,Animate的构造函数接受一个参数:即将运动起来的DOM节点

   var Animate = function(dom){
       this.dom = dom;
       this.startTime = 0;
       this.startPos = 0;
       this.endPos = 0;
       //dom节点需要被改变的css属性名
       this.propertyName = null;
       //缓动算法
       this.easing = null;
       //动画持续时间
       this.duration = null;  
   }
   Animate.prototype.start = function(propertyName, endPos, duration, easing){
       this.startTime = new Date.getTime();
       //getBoundingClientRect
       this.startPos = this.dom.getBoundingClientRect()[propertyName];   //dom节点初始位置
       this.propertyName = propertyName;
       this.endPos = endPos;
       this.duration = duration;
       //缓动算法
       this.easing = tween[easing];
       var self = this;
       var timeId = setInterval(function(){
           //如果动画已结束,则清除定时器
           //这里直接进行了动画调用
           if(self.step() === false){
               clearInterval(timeId);
           }
       }, 19);
   }
   Animate.prototype.step = function(){
       var t = new Date().getTime();
       if(t >= this.startTime + this.duration){
           //更新小球的css属性值
           this.update(this.endPos);
           return false;
       }
       var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration);
       //pos为小球当前位置
       this.update(pos);  //更新小球的css属性值
   }

最后是负责更新小球属性值的Animate.prototype.update方法

   Animate.prototype.update = function(pos){
       this.dom.style[this.propertyName] = pos + 'px';   
   }
   //测试代码
   var div = document.getElementById('div');
   var animate = new Animate(div);
   animate.start('left', 1500, 500, 'strongEaseIn');

我们最终通过对象来定义算法,并且通过属性传入调用对象中,达到了方便替换各种算法的效果。

表单校验

我们首先要将表单的校验逻辑都封装成策略对象

   var strategies = {
       isNonEmpty: function(value, errorMsg){
          if(value === ''){
              return errorMsg;
          }
       },
       minLength: function(value, length, errorMsg){
          if(value.length < length){
              return errorMsg;
          }
       },
       isMobile: function(value){
           if(!/(^1[3|5|8][0-9]{9}$)/.test(value)){
               return errorMsg;
           }
       }
   }

然后实现一个validataFunc类

   var Validator = function(){
       //保存校验规则
       this.cache = [];
   };
   Validator.prototype.add = function(dom, rule, errorMsg){
       //把strategy和参数分开
       var arr = rule.split(':');
       this.cache.push(function(){
          //statefy: minLength
          var strategy = arr.shift();
          //[dom.value, 10]
          arr.unshift(dom.value);
          //[dom.value, 10, errorMsg]
          arr.push(errorMsg);
          //return minLength.apply(dom, [dom.value, 10, errorMsg])
          return strategies[strategy].apply(dom, arr);
       })
   };
   Validator.prototype.start = function(){
      for(var i = 0; i <  this.cache.length; i++;){
         var msg =  this.cache[i];
         //如果有确切的返回值,说明校验没有通过
         if(msg){
             return msg;
         }
      }
   }

这样重构之后,我们在修改某个校验规则的时候,只需要编写或者改写少量的代码

   validator.add(registerForm.userName, 'minLength: 10', '用户名长度不能小于10位');

策略模式也有一些缺点,使用策略模式会在程序中增加许多策略对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值