一文读懂开闭原则的最佳实现(之一)——策略模式

656 阅读3分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

何为策略模式

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

在我首次看定义的时候,就觉得是一系列的分支语句,比如:

function stragey(key){
switch(key){
    case 'a': return 1;
    case 'b':return 2;
    ....
}
}

stragey('b')

但如果要增加新的策略 c,就要深入函数内部,在switch语句中重新加一个分支。

使用策略模式重构

如果我们要重构一段代码,就要把可变的部分与不可变的部分区别开来,一个策略模式至少由两个部分组成,一是策略类,二是环境类。策略类我们都了解了,他是封装了具体的算法,负责具体的计算过程;环境类,就是接受客户的请求,然后把请求委托给某个策略。

我们从类的角度来实现一下:

// 策略类
class strategyA{...}

class strategyB{...}
// 环境类
class Context{
    constructor(){
        this.obj=null
        this.strategy=null
    }
    setObj=(obj)=>{
        this.obj=obj
    }
    setStrategy=(strategy)=>{
        this.strategy=strategy
    }
    getResult=()=>{
        return  this.strategy.calculate(this.obj)
    }
}

js 版本的策略模式实例

比如说,公司年底要做绩效统计生成年终奖,如果考评为 S 的会发 6 个月的工资并列入年度最佳员工列表,考评为 A 的会发 4 个月工资,考评为 B 的会发两个月工资,考评为 C 的会发 1 个月的工资,考评为 D 的直接解雇。

比如说我们的员工列表:

const employeeList = [
  { name: "朱一旦", level: "S", salary: 50 },
  { name: "马小男", level: "A", salary: 15 },
  { name: "马小玲", level: "B", salary: 7 },
  { name: "马小浩", level: "D", salary: 30 },
];

上一篇文章单例模式中说过,在 js 里写类生成唯一一个对象,如同“脱裤放屁————多此一举”,不如直接定义一个“对象”就好了,不同策略即是对象中不同的方法。

年终奖策略:

const FireList=[]

const strategy={
    'S':(employee)=>{
        pushToBestList(employee)
        return employee.salary*6
    }
    'A':(employee)=>{
        return employee.salary*4
    }
    'B':(employee)=>{
        return employee.salary*2
    }
    'C':(employee)=>{
        return employee.salary*1
    }
    'D':(employee)=>{
        pushToFireList(employee)
        return 0
    }
}

我们要给财务一份年终表的表单,就可以这样计算:

const yearEndSalaryList=employeeList.map(employee=>{
    const {level}=employee
    return strategy[level](employee)
})

console.log(yearEndSalaryList) // 年终奖汇总
console.log(FireList) //解雇名单

用策略模式重构你的表单

如果你写过 vanillaJS,对以下表单校验的代码绝对不陌生:

const form = document.getElementById("form");
form.onsubmit = () => {
  if (!form.accont.value) {
    alert("账户名不能为空");
    return;
  }

  if (form.password.value.length < 10) {
    alert("密码长度需大于10位");
    return;
  }
};

这里我们直接获取表单的某些值,分别对某个属性进行处理。这段代码非常常见,但他的可复用性并不强,如果需要多个表单又得复制一通。那我们用策略模式将其整理一下:

这里的逻辑是:校验不通过则抛出带有错误信息的错误。

const createError = (msg) => {
  const err = new Error();
  err.msg = msg;
  return err;
};

const strategy = {
  required: (value, message) => {
    if (!value || value.length === 0) {
      throw createError(message);
    }
  },
  minLength: (value, message, len) => {
    if (!value || value.length < len) {
      throw createError(message);
    }
  },
};

这样,策略模式中的策略类就 ok 了,接下来实现环境类。表单里的环境类,用于将表单数据委托给策略类进行处理。这里的逻辑是,先要将每个委托逐一与策略绑定,然后统一执行。

class Validator {
  constructor() {
    this.cache = [];
  }

  add(val, rule, msg) {
    const [strategyName, param = null] = rule.split(":");
    this.cache.push(function () {
      strategy[strategyName].apply(null, [val, msg, param]);
    });
  }

  validate() {
    this.cache.forEach((val) => {
      val();
    });
  }
}

const validate = (form) => {
  const validator = new Validator();
  validator.add(form.account, "required", "账户名不能为空");
  validator.add(form.pswd, "minLength:10", "密码长度需大于10位");

  try {
    validator.validate();
  } catch (err) {
    alert(err.msg);
  }
};
const mockForm = {
  account: "",
  pswd: "11",
};

validate(mockForm);

当然一个表单属性可能有多个校验规则,我们可以通过多加策略的方式进行多种规则校验,如下

const validate = (form) => {
    const validator = new Validator();
    validator.add(form.account, "required", "账户名不能为空");
    validator.add(form.account, "minLength:5", "账户长度需大于5位");
    validator.add(form.pswd, "minLength:10", "密码长度需大于10位");
  ......
};

也可以通过类似于 antd 表单校验规则的方式:

const stockValidator = (_: any, value: string) => {
  if (
    isNaN(Number(value)) ||
    value.length !== 6 ||
    (value[0] !== "3" && value[0] !== "6" && value[0] !== "0")
  )
    return Promise.reject("请输入正确的代码");
    return Promise.resolve();
};
...
const rules = [
  { required: true, message: "输入股票代码" },
  { validator: stockValidator },
];
....

总结

策略模式利用组合、委托、多态来避免多重条件分支,使代码复用性更强,也是开放封闭原则的最好支持之一。但策略模式的缺点是,会在程序中增加许多策略类,每个策略类中的每条策略之间也必须清楚他们的区分。

最近在看《js 设计模式与开发实践》,本文是我对此书的一些概括与扩展。下一篇文章写代理模式~~