js设计模式之策略模式

栏目: 后端 · 发布时间: 5年前

内容简介:策略模式的定义是:比如要实现从上海到广州,既可以坐火车,又可以坐高铁,还可以坐飞机。这取决与个人想法。在这里,不同的到达方式就是不同的策略,个人想法就是条件。以计算奖金为例,绩效为S的年终奖是4倍工资,绩效为A的年终奖是3倍工资,绩效为B的年终奖是2倍工资。那么这里奖金取决于两个条件,绩效和薪水。最初编码实现如下:

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

比如要实现从上海到广州,既可以坐火车,又可以坐高铁,还可以坐飞机。这取决与个人想法。在这里,不同的到达方式就是不同的策略,个人想法就是条件。

1.计算奖金

以计算奖金为例,绩效为S的年终奖是4倍工资,绩效为A的年终奖是3倍工资,绩效为B的年终奖是2倍工资。那么这里奖金取决于两个条件,绩效和薪水。最初编码实现如下:

const calculateBonus =  (performanceLevel, salary) => {
  if(performanceLevel === 'S') {
    return salary * 4;
  }

  if(performanceLevel === 'A') {
    return salary * 3;
  }

  if(performanceLevel === 'B') {
    return salary * 2;
  }
}
复制代码

这段代码十分简单,但存在显而易见的缺点。

  1. if语句过多,需要涵盖所有的条件。
  2. 弹性差,如果新增绩效C,那么需要什么calculateBonus的内部实现去修改代码,不符合开放封闭原则。
  3. 复用性差,计算奖金的算法不能直接复用,除非复制粘贴。

2.使用策略模式

策略模式是指定义一系列的算法,并将它们封装起来,这很符合开闭原则。策略模式的目的就是将算法的使用和算法的实现分离出来。

一个基于策略模式的程序至少由两部分组成。第一个部分是策略类,它封装了具体的算法,并负责计算的具体过程。第二个部分是环境类Context,Context接受客户的请求,随后将请求委托给某一个策略类。要做到这点,Context中需要维持对某个策略对象的引用。

现在使用策略模式来重构以上代码,第一个版本是基于class,第二个版本是基于函数。

2.1基于class

class PerformanceS {
  calculate(salary) {
    return salary * 4;
  }
}

class PerformanceA {
  calculate(salary) {
    return salary * 3;
  }
}

class PerformanceB {
  calculate(salary) {
    return salary * 2;
  }
}

class Bonus {
  constructor(strategy, salary) {
    this.strategy = strategy;
    this.salary = salary;
  }

  getBonus() {
    if(!this.strategy) {
      return -1;
    }
    return this.strategy.calculate(this.salary);
  }
}

const bonus = new Bonus(new PerformanceA(), 2000);
console.log(bonus.getBonus()) // 6000
复制代码

它没有上述的三个缺点。在这里,有三个策略类,分别是PerformanceS、Performance A、PerformanceB。这里的context就是Bonus类,它接受客户的请求(bonus.getBonus),将请求委托给策略类。它保存着策略类的引用。

2.2基于函数

上述中,每一个策略都是class,实际上,class也是一个函数。这里,可以直接用函数实现。

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

const getBonus = (performanceLevel, salary) => strategies[performanceLevel](salary)

console.log(getBonus('A', 2000)) // 6000
复制代码

3.多态在策略模式中的体现

通过使用策略模式重构代码,消除了程序中大片的条件语句。所有和奖金相关的逻辑都不在Context中,而是分布在各个策略对象中。Context并没有计算奖金的能力,当它接收到客户的请求时,它将请求委托给某个策略对象计算,计算方法被封装在策略对象内部。当我们发起“获得奖金”的请求时,Context将请求转发给策略类,策略类根据客户参数返回不同的内容,这正是对象多态性的体现,这也体现了策略模式的定义--“它们可以相互替换”。

4.计算小球的缓动动画

我们的目标是编写一个动画类和缓动算法,让小球以各种各样的缓动效果在页面中进行移动。

很明显,缓动算法是一个策略对象,它有几种不同的策略。这些策略函数都接受四个参数:动画开始的位置s、动画结束的位置e、动画已消耗的时间t、动画总时间d。

const tween = {
  linear: (s, e, t, d) => { return e*t/d + s },
  easeIn: () => { /* some code */ },
  easeOut: () => { /* some code */ },
  easeInOut: () => { /* some code */ },
}
复制代码

页面上有一个div元素。

<div id='div' style='position: absolute; left: 0;'></div>
复制代码

现在要让这个div动起来,需要编写一个动画类。

const tween = {
  linear: () => { /* some code */ },
  easeIn: () => { /* some code */ },
  easeOut: () => { /* some code */ },
  easeInOut: () => { /* some code */ },
}

class Animation {
  constructor(dom) {
    this.dom = dom;
    this.startTime = 0;
    this.startPos = 0;
    this.endPos = 0;
    this.propertyName = null;
    this.easing = null;
    this.duration = null;
  }

  // 开始动画
  start(propertyName, endPos, duration, easing) {
    this.startTime = Date.now();
    // 初始化参数,省略其他
    const self = this;
    // 循环执行动画,如果动画已结束,那么清除定时器
    let timer = setInterval(() => {
      if(self.step() === false) {
        clearInterval(timer);
      }
    }, 1000/60);
  }

  // 计算下一次循环到的时候小球位置
  step() {
    const now = Date.now();
    if(now > this.startTime + this.duration) {
      return false;
    } else {
      // 获得小球在本次循环结束时的位置并更新位置
      // const pos = this.easing();
      // this.update(pos);
    }
  }

  update(pos) {
    this.dom.style[propertyName] = pos + 'px';
  }
}
复制代码

具体实现略去。这里的Animation类就是环境类Context,当接收到客户的请求(更新小球位置 self.step()),它将请求转发给策略内(this.easing()),策略类进行计算并返回结果。

5.更广义的“算法”

策略模式指的是定义一系列的算法,并且把他们封装起来。上述所说的计算奖金和缓动动画的例子都封装了一些策略方法。

从定义上看,策略模式就是用来封装算法的。但如果仅仅将策略模式用来封装算法,有些大材小用。在实际开发中,策略模式也可以用来封装一些的“业务规则”。只要这些业务规则目标一致,并且可以替换,那么就可以用策略模式来封装它们。以使用策略模式来完成表单校验为例。

6.表单校验

<form action='xxx' id='form' method='post'>
  <input type='text' name='username'>
  <input type='password' name='passsword'>
  <button>提交</button>
</form>
复制代码

验证规则如下:

const form = document.querySelector('form')
form.onsubmit = () => {
  if(form.username.value === '') {
    alert('用户名不能为空')
    return false;
  }

  if(form.password.value.length < 6) {
    alert('密码不能少于6位')
    return false;
  }
}
复制代码

这是一种很常见的思路,和最开始计算奖金一样。缺点也是一样。

6.1使用策略模式重构表单校验

第一步需要把这些校验逻辑封装成策略对象。

const strategies = {
  isNonEmpty: (value, errMsg) => {
    if(value === '') {
      return errMsg
    }
  },
  minLength: (value, errMsg) => {
    if(value.length < minLength) {
      return errMsg
    }
  }
}
复制代码

第二步对表单进行校验。

class Validator {
  constructor() {
    this.rules = [];
  }
  
  add(dom, rule, errMsg) {
    const arr = rule.split(':');
    this.rules.push(() => {
      const strategy = arr.shift();
      arr.unshift(dom.value);
      arr.push(errMsg);
      return strategies[strategy].apply(dom, arr);
    })
  }

  start() {
    for(let i = 0, validatorFunc; validatorFunc = this.rules[i++];) {
      let msg = validatorFunc();
      if(msg) {
        return msg;
      }
    }
  }

}

const form = document.querySelector('form')
form.onsubmit = (e) => {
  e.preventDefault();
  const validator = new Validator();
  validator.add(form.username, 'isNonEmpty', '用户名不能为空');
  validator.add(form.password, 'minLength:6', '密码长度不能小于6位');
  const errMsg = validator.start();
  if(errMsg) {
    alert(errMsg);
    return false;
  }
}
复制代码

上述例子中,校验逻辑是策略对象,其中包含策略的实现函数。Validator类是Context,用于将客户的请求(表单验证)转发到策略对象进行验证。与计算奖金的Bonus不同的是,这里并没有将验证参数通过构造函数传入,而是通过validator.add传入相关验证参数,通过validator.start()进行验证。

策略模式优缺点

  1. 策略模式利用组合、委托和多态等技术和思想,可以有效避免多重选择语句。
  2. 策略模式通过扩展策略类,对开放封闭原则完全支持,使得它们易于切换和扩展。
  3. 策略模式的算法可以用在其他地方,避免复制。
  4. 策略模式利用组合和委托让Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案。

前三点正是开头实现的 计算奖金 函数的缺点。 策略模式有一点缺点,不过并不严重。

  1. 会增加策略类或者策略对象,增加了复杂度。但是与Context解耦了,这样更便于扩展。
  2. 使用策略模式,必须了解所有的策略以便选择合适的策略,这是strategies要向客户暴露它的所有实现,不符合最少知识原则。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ruby for Rails

Ruby for Rails

David Black / Manning Publications / 2006-05-11 / USD 44.95

What's Inside * How Ruby and Rails work, separately and together * Extensive Ruby language tutorial * Ruby techniques for Rails applications * Explore the Rails framework source code A new level of pr......一起来看看 《Ruby for Rails》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具