胤的博客
CC BY-NC-SA 4.0

©️ 2023 胤 版权所有

banner

所念皆星河

avatar

JavaScript:策略模式的小应用

2023.11.13

最近在开发项目时,负责人提了一个需求:自动判断用户提交的表单内容,然后根据内容给每个表单打上一个或多个标签。刚开始时,我用 if...else... 写出了屎一样的代码:

const verifier = (input: string) => {
  if (!input) return;

  const tags: string[] = [];

  if (...) {
    tags.push("...");
  } else if (...) {
    tags.push("...");
  } else {
    tags.push("...");
  }

  return tags;
};

这段代码的确能够满足需求,根据 input 的值在数组中插入合适的标签,最后再返回数组。但是,这段代码的可拓展性非常糟糕,负责人在 review 的时候问我:“假如我需要拓展需求呢?比如不只是前面的那些标签,我之后还要加上更多的标签。”我当时回了一句,直接在后面继续 if...else... 就好了 🙈。即便不讨论可拓展性,这段代码的可读性也一言难尽,可以通过 switch...case... 的方法来提高代码的可读性:

const verifier = (input: string) => {
  if (!input) return;

  const tags: string[] = [];

  switch (input.xxx) {
    case "xxx":
      tags.push("...");
      break;
    case "xxx":
      tags.push("...");
      break;
    default:
      tags.push("...");
  }

  return tags;
};

但这种方法不能从根本上解决可拓展性差的问题。在写一些功能性的方法时,例如上述例子中的标签验证器,最好的做法是将每个验证器单独拆出来,一个验证器只负责一个功能。这种做法能够保证后续添加的验证器也是独立的,并且在使用时,可以自由选择不同验证器进行组合。这就是策略模式,具体的代码实现如下:

type verifier = {
  name: string;
  verifier: (input: string) => boolean;
};

class verifierClass {
  nullVerifier = (input: string) => !!input;

  numberVerifier = (input: string) => {
    if (this.nullVerifier(input)) {
      return false;
    }

    const numberRegex = /^[0-9]+$/;
    return numberRegex.test(input);
  };

  letterVerifier = (input: string) => {
    if (this.nullVerifier(input)) {
      return false;
    }

    const letterRegex = /^[a-zA-Z]+$/;
    return letterRegex.test(input);
  };

  letterLengthVerifier = (input: string, len: number) => {
    if (this.nullVerifier(input)) {
      return false;
    }

    if (input.length !== len) {
      return false;
    }

    return this.letterVerifier(input);
  };
}

const basicVerifier = new verifierClass();

const verifiers: verifier[] = [
  {
    name: "X",
    verifier: (input: string) => basicVerifier.letterLengthVerifier(input, 1),
  },
  {
    name: "XX",
    verifier: (input: string) => {
      if (basicVerifier.nullVerifier(input)) {
        return false;
      }

      const mixRegex = /^[a-zA-Z0-9]+$/;
      return mixRegex.test(input);
    },
  },
  {
    name: "XXX",
    verifier: (input: string) => {
      if (basicVerifier.nullVerifier(input)) {
        return false;
      }

      const base64Regex =
        /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
      return base64Regex.test(input);
    },
  },
];

const verifierFunc = (input: string, verifiers: verifier[]) => {
  const tags: string[] = [];

  verifiers.reduce((prev, current) => {
    if (current.verifier(input)) {
      return [...prev, current.name];
    }
    return prev;
  }, tags);

  return tags;
};

上述代码中,verifierClass 类里面是基本的验证器,更高级的验证器可以由这些基本的验证器组成;verifiers 数组是目前使用的验证器集合,如果后续需求有扩展,可以在这个数组中添加新的验证器,或者新写一个数组,使两个验证器集合互不干扰;verifierFunc 是验证函数,这个函数接受待验证的输入和验证器数组两个参数,通过数组的 reduce 方法让输入经过每个验证器,符合的就打上标签。

相比于 if...else...,策略模式对每段代码的功能拆分得更加细致,自定义性也比较好。例如上述代码中 verifiers 这个数组,它的验证器可以直接使用 verifierClass 类中的基础验证器——“空验证器”、“数字验证器”、“字母验证器”......也可以在基础验证器上进行自定义,生成 “base64 验证器”等。在开头的 if...else... 模式中,我们新增需求修改的是 verifierFunc 函数,在策略模式下,这段函数是不需要改动的,只需要更新验证器参数就行,我觉得策略模式才是真正做到了代码的解耦。

通过这一次开发,我发现自己对如何优化代码并不熟悉,策略模式也是在负责人的提醒下才想出的,看来代码写得还是不够呀!

评论