设计模式之策略模式(JavaScript版本)

227 阅读4分钟

本文档已更新于 【前端橘子君】 【Github】

定义

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

绩效奖金案例

策略模式说的简单一点,就是根据现有条件的不同,选择不同策略来完成目标功能。

例如我要知道一个直角三角形的面积,我可以根据两条直角边进行求解,也可以根据一条边和一个角求解,甚至更多解法,就看题目给出哪些已知条件,这就是策略模式。

又举个例子,我要去一个地方旅行,我可以坐火车,汽车,高铁,飞机,甚至可以骑自行车,这些我们都可以称之为策略,至于选择哪种策略,我们可以根据时间和金钱预算来选择不同的策略。

又比如,公司想要根据一个员工的绩效等级和工资来算年终奖,那么绩效等级和比例我们可以称之为策略,用代码来看看。

// 原始版本
function annualBonus (performance, salary) {
  if (performance === 'A') {
    return salary * 1.2; // 优秀则发放120%的奖金
  } else if (performance === 'B') {
    return salary; // 良好则全额发放
  } else if (performance === 'C') {
    return salary * 0.8; // 一般则发放80%
  }
  return 0; // 如果不及格则不发放奖金
}

console.log(annualBonus('A', 12000))
console.log(annualBonus('C', 8000))
console.log(annualBonus('D', 30000))

从上边的代码可以看到,该函数根据不同的绩效等级提供了不同的算法来实现该功能,同时,其弊端也显而易见。

  • 代码体积非常大,且包含了多个if-else
  • 如果新增一个绩效,需要修改annualBonus的内容 我们接下来用策略模式对其进行重构

策略模式重构年终奖案例

策略模式包含包含至少两部分:策略组、环境

所谓的策略组,拿上面的例子来说就是根据不同的绩效等级提供的算法,而环境就是已有的已知条件,即员工的绩效等级和薪资。

// 定义策略组
var strategyGroup = {
  'A': function (salary) {
    return salary * 1.2;
  },
  'B': function (salary) {
    return salary;
  },
  'C': function (salary) {
    return salary * 0.8;
  },
  'D': function (salary) {
    return 0;
  }
}

// 定义环境
var annualBonus = function (level, salary) {
  return strategyGroup[level](salary)
}

// 调用
console.log(annualBonus('A', 12000))
console.log(annualBonus('C', 8000))
console.log(annualBonus('D', 30000))

策略模式将具体实现方式从调用函数中抽离出来了,减少了if-else这样的多重调用,另外如果需要新增一种绩效,也不需要动环境中的代码,只需要在策略组中新增就行,增加了容错率,减少了环境的代码体积。

表单验证案例

背景:验证名称非空,手机号码格式合法,如果不合法则不予通过。

先模拟一个用户表单输入

var userName = '前端橘子君';
var phone = '14200000000';
// 原始代码
function check () {
  if (!userName) {
    return false;
  }
  if (!/^1(3|5|6|7|8|9)[0-9]{9}$/.test(phone)) {
    return false;
  }
  return true;
}

弊端就不多说了,和年终奖案例是一样的,下边我们来对其用策略模式进行重构。

策略模式重构表单验证案例

// 定义策略组
var strategyGroup = {
  userName: function () {
    return !!userName;
  },
  phone: function () {
    return !!/^1(3|5|6|7|8|9)[0-9]{9}$/.test(phone);
  }
}

// 定义环境
function check (args) {
  var checkList = args.map(item => strategyGroup[item]);
  return checkList.every(ele => ele());
}

// 调用
var result = check(['userName', 'phone']);
console.log(result);

在该案例中,每一种校验规则都是一种策略,而校验字段就是环境,如果有需要新增校验,则直接在strategyGroup中新增即可,我们可以将这个封装为公共方法,这样没必要每个页面都写一遍验证规则了

上面的内容过于简单,不建议在项目中直接使用,可以改造一下。

// 定义策略组
var strategyGroup = {
  userName: function (userName) {
    return !!userName;
  },
  phone: function (phone) {
    return !!/^1(3|5|6|7|8|9)[0-9]{9}$/.test(phone);
  }
}

// 定义环境
function check (args) {
  return args.map(item => strategyGroup[item.label](item.value)).every(ele => ele);
}

// 调用
var checkOptions = [
  {
    label: 'userName', // 需要校验的字段名
    value: '前端橘子君' // 用户输入的值
  },
  {
    label: 'phone', // 需要校验的字段名
    value: '13000000000' // 用户输入的值
  }
]
var result = check(checkOptions);
console.log(result);

优势

  • 1、算法可以自由切换。
  • 2、避免使用多重条件判断。
  • 3、扩展性良好。

缺点

  • 1、策略会逐渐增多
  • 2、所有策略需要向外暴露

更多相关文档,请见:

线上地址 【前端橘子君】

GitHub仓库【前端橘子君】