在JavaScript中理解策略模式

设计模式是: 在面向对象软件过程当中针对特定问题的简洁而优雅的解决方案. 经过对封装、继承、多态、组合等技术的反复利用, 提炼出可重复使用面向对象的设计技巧.算法

JavaScript 能够模拟实现传统面向对象语言的设计模式. 事实上也的确如此, 好多的代码 demo 都是沿着这个思路分析的. 看完后内心难免有种一万头🦙在奔腾, 还顺便飘来了六个字, 走(qu)你(de), 设计模式.设计模式

然而仅仅是生搬硬套, 未免会失去 JavaScript 的灵活性. 不如溯本求源, 看看这些设计模式到底在传达什么, 而后遵循此点.函数

策略模式定义

策略模式: 定义一系列的算法, 把它们一个个封装起来, 而且使它们能够相互替换.ui

字面意思, 就是定义封装多种算法, 且各个算法相互独立. 固然, 也不单单是算法. 只要定义一些规则, 经处理后输出咱们想要的结果就成. 在此咱们称单个封装后的算法为一个策略. 一系列封装后的算法称为一组策略.spa

一个基于策略模式的程序至少由两部分组成. 第一部分是一组策略类, 策略类封装了具体的算法, 并负责具体的计算过程. 第二部分是环境类 Context, Context 接受客户的请求, 随后把请求委托给某一个策略类.设计

这是面向传统面向对象语言中的说法. 在面向对象思想中, 经过对组合, 多态等技术的使用来实现一个策略模式. 在 JavaScript 中, 对于一个简单的需求来讲, 这么作就有点大材小用了.code

因此, 上面的那句话, 咱们换种说法就是, 策略模式须要至少两部分, 一部分是保存着一组策略. 另外一部分则是如何分配这些策略, 即如何把请求委托给某个/些策略. 其实这也是策略模式的目的, 将算法的使用与算法的实现分离.对象

评级

快到年末了, 公司打算制定一个标准用来给员工评级发福利.继承

考核项目\等级
A 100>a>=90 90>a>=80 80>a>=70
B 100>b>=90 90>b>=80 80>b>=70

以A、B考核项目来评定甲乙丙等级. 现有考核人员:ip

考核项目\考核人 person_1 person_2 person_3
A 80 93 92
B 85 70 90
const persons = [
  { A: 80, B: 85 },
  { A: 93, B: 70 },
  { A: 92, B: 90 }
]
复制代码

常规操做

甲乙丙等级对 A、B 的分值要求是不同的. 因此咱们能够这么作:

function rating(person) {
  let a = person.A;
  let b = person.B;
  if (a >= 90 && b >= 90) {
    return '甲';
  } else if (a >= 80 && b >= 80) {
    return '乙';
  } else if (a >= 70 && b >= 70) {
    return '丙'
  } else {
    console.log('想啥呢, 还不赶忙卷铺走人');
  }
}
persons.forEach(person => {
  person.rate = rating(person);
})
// > persons
// [ { A: 80, B: 85, rate: '乙' },
// { A: 93, B: 70, rate: '丙' },
// { A: 92, B: 90, rate: '甲' } ]
复制代码

策略模式下的评级

在策略模式中一部分, 咱们提到的分配策略. 要想分配策略, 首先就要知道全部的策略, 只有这样咱们才能针对性的委托给某个/些策略. 这, 也是策略模式的一个缺点.

若是换成策略模式, 第一部分就是保存一组策略. 如今咱们以甲乙丙三种定级标准来制定三种策略, 用对象 strategies 来存贮策略. 考虑到之后可能有 D、E、F 等考核项目的存在, 咱们稍微改一下:

const strategies = {
  '甲': (person, items) => {
    const boolean = items.every(item => {
      return person[item] >= 90;
    });
    if (boolean) return '甲';
  },
  '乙': (person, items) => {
    const boolean = items.every(item => {
      return person[item] >= 80;
    });
    if (boolean) return '乙';
  },
  '丙': (person, items) => {
    const boolean = items.every(item => {
      return person[item] >= 70;
    });
    if (boolean) return '丙';
  }
}
复制代码

策略就制定好了. 对象的键对应着策略的名称, 对象的值对应着策略的实现.然而, 咱们发现, 任何一个策略都不能单独完成等级的评定.

但是, 咱们有说一组策略只能选择其中一个么? 为了达成某个目的, 策略组封装了一组相互独立平等替换的策略. 一个策略不行, 那就组合呗. 这也是策略模式另外一部分存在的意义, 即如何分配策略. rating 函数封装了如何委托策略.

function rating(person, items) {
  return strategies['甲'](person, items)
    || strategies['乙'](person, items)
    || strategies['丙'](person, items)
}
persons.forEach(person => {
  person.rate = rating(person, ['A', 'B'])
})
// > persons
// [ { A: 80, B: 85, rate: '乙' },
// { A: 93, B: 70, rate: '丙' },
// { A: 92, B: 90, rate: '甲' } ]
复制代码

逻辑的转移

全部的设计模式都遵循一条原则. 即 “找出程序中变化的地方, 并将变化封装起来”.

将不变的隔离开来, 变化的封装起来. 策略模式中, 策略组对应着程序中不变的地方. 将策略组制定好存贮起来, 而后想着如何去分配使用策略.

固然, 如何制定策略和如何分配策略之间的关系十分紧密, 能够说二者相互影响.

再次看看制定的策略, “找出程序中变化的地方, 并将变化封装起来”, 咱们能够再次改造一下策略组对象 strategies 和负责分配策略的函数 rating.

const strategies = {
  '甲': 90,
  '乙': 80,
  '丙': 70,
}
function rating(person, items) {
  const level = value => {
    return (person, items) => {
      const boolean = items.every(item => {
        return person[item] >= strategies[value];
      });
      if (boolean) return value;
    }
  }
  return level('甲')(person, items)
    || level('乙')(person, items)
    || level('丙')(person, items)
}

persons.forEach(person => {
  person.rate = rating(person, ['A', 'B'])
})
// > persons
// [ { A: 80, B: 85, rate: '乙' },
// { A: 93, B: 70, rate: '丙' },
// { A: 92, B: 90, rate: '甲' } ]
复制代码

在上面的这种作法中, 咱们把制定策略的逻辑挪到了分配策略里了. 因此说, 如何制定策略和如何分配策略, 依状况而定.

不过回头在看一看这段代码, 是否是和平时用对象映射的作法很类似.

固然, 策略模式的用法还有不少, 使用形式也有不一样, 要根据场景找到最适合的策略.

小结

总结一下:

  1. 策略模式至少包括两部分, 制定策略和分配策略.

  2. 策略模式的目的在于, 将策略制定和策略分配隔离开来.

  3. 策略制定和策略分配关系密切, 相互影响.

相关文章
相关标签/搜索