从form表单来看策略模式

594 阅读5分钟

前言

之前写程序的时候为了快速开发写了许多ifelse if的语句,当时开发倒是挺爽的,事后维护的时候想骂人,为了不坑自己和其他小伙伴,下面从零开始用一个基础表单 + 策略模式清除if分支语句

<form class="cs-form">
  <label>
    <span>账号:</span>
    <input name="account" type="text" />
  </label>
  <label>
    <span>密码:</span>
    <input name="password" type="password" />
  </label>
  <label>
    <span>手机号:</span>
    <input name="mobile" type="number" />
  </label>
  <button class="submit" type="submit">登录</button>
</form>

上面是一个很常见的表单元素,里面有账号和密码以及手机号,我们需要在提交之前对他进行一些校验,这里默认的规则是账号和密码不能为空,手机号的长度必须是 11 的纯数字。

第一版

interface Iform {
  [k: string]: any;
  account: HTMLInputElement;
  password: HTMLInputElement;
  mobile: HTMLInputElement;
}
const form = document.querySelector(".cs-form") as HTMLFormElement;

form.addEventListener("submit", function(e) {
  e.preventDefault();
  const { account, password, mobile } = (form as unknown) as Iform;
  if (!account.value) {
    // 提示
    alert("请输入账号");
    return false;
  }
  if (!password.value) {
    // 提示
    alert("请输入密码");
    return false;
  }
  if (!/\d{11}/.test(mobile.value)) {
    // 提示
    alert("请输入正确的手机号");
    return false;
  }
  return false;
});

根据上面的要求,我们实现了第一版代码,从代码的实现看毫无疑问是正确的,为了对比下面再用策略模式实现以下上述的功能

第二版

interface Iform {
  [k: string]: any;
  account: HTMLInputElement;
  password: HTMLInputElement;
  mobile: HTMLInputElement;
}

interface Ilist {
  value: string;
  type: name;
  msg: string;
}

type name = keyof typeof rule;
const rule = {
  account(value: string, msg: string) {
    if (!value) {
      return msg;
    }
  },
  password(value: string, msg: string) {
    if (!value) {
      return msg;
    }
  },
  mobile(value: string, msg: string) {
    if (!/\d{11}/.test(value)) {
      return msg;
    }
  }
};
class Verification {
  private list: Ilist[] = [];
  public message: string;
  add<T extends { value: string }>(dom: T, type: name, msg: string) {
    this.list.push({
      value: dom.value,
      type: type,
      msg: msg
    });
  }
  start(): boolean {
    for (let i = 0; i < this.list.length; i++) {
      const { value, type, msg } = this.list[i];
      const fn = rule[type](value, msg);
      if (fn) {
        this.message = msg;
        return false;
      }
    }
    this.message = "";
    return true;
  }
}

const form = document.querySelector(".cs-form") as HTMLFormElement;

form.addEventListener("submit", function(e) {
  e.preventDefault();
  const { account, password, mobile } = (form as unknown) as Iform;

  const ruleSet = new Verification();
  ruleSet.add(account, "account", "请输入账号");
  ruleSet.add(password, "password", "请输入密码");
  ruleSet.add(mobile, "mobile", "请输入正确的手机号");

  if (!ruleSet.start()) {
    // 提示
    alert(ruleSet.message);
    return false;
  }
  return false;
});

从代码量看比第一版多出了很多语句,这里做法是把代码的规则放置在 rule 对象中,因为 javascript 本身就是这么灵活不需要在使用传统语言的 class 来实现了,剩下的就是新建一个校验类,新增校验规则最后校验,当然现在验证规则还是比较少,看不出策略模式的优点。

下面再来新增一个要求,密码长度不能小于 6 位,在第一版中我们要紧接在密码不能为空的校验之后写,再来看下策略模式新增这个验证会新增哪些代码。

interface Iform {
  [k: string]: any;
  account: HTMLInputElement;
  password: HTMLInputElement;
  mobile: HTMLInputElement;
}

interface Ilist {
  value: string;
  type: name;
  msg: string;
}

type name = keyof typeof rule;
const rule = {
  account(value: string, msg: string) {
    if (!value) {
      return msg;
    }
  },
  password(value: string, msg: string) {
    if (!value) {
      return msg;
    }
  },
  // 密码长度小于6的时候提示
  passwordMinLength(value: string, msg: string) {
    if (value.length < 6) {
      return msg;
    }
  },
  mobile(value: string, msg: string) {
    if (!/\d{11}/.test(value)) {
      return msg;
    }
  }
};
class Verification {
  private list: Ilist[] = [];
  public message: string;
  add<T extends { value: string }>(dom: T, type: name, msg: string) {
    this.list.push({
      value: dom.value,
      type: type,
      msg: msg
    });
  }
  start(): boolean {
    for (let i = 0; i < this.list.length; i++) {
      const { value, type, msg } = this.list[i];
      const fn = rule[type](value, msg);
      if (fn) {
        this.message = msg;
        return false;
      }
    }
    this.message = "";
    return true;
  }
}

const form = document.querySelector(".cs-form") as HTMLFormElement;

form.addEventListener("submit", function(e) {
  e.preventDefault();
  const { account, password, mobile } = (form as unknown) as Iform;

  const ruleSet = new Verification();
  ruleSet.add(account, "account", "请输入账号");
  ruleSet.add(password, "password", "请输入密码");
  ruleSet.add(password, "passwordMinLength", "密码长度不符合要求");
  ruleSet.add(mobile, "mobile", "请输入正确的手机号");

  if (!ruleSet.start()) {
    // 提示
    alert(ruleSet.message);
    return false;
  }
  return false;
});

把密码不能小于六位的规则添加到rule中,之后 add 的时候添加它,这里基本的校验其实就完成了,不过从上面看,我们的校验肯定不是一个 input 就对应一条,实际上会存在多种校验,比如最小长度最大长度,不能包含敏感词等,添加多条规则也是很合理的要求,下面就是最终版的实现。

最终版

interface Iform {
  [k: string]: any;
  account: HTMLInputElement;
  password: HTMLInputElement;
  mobile: HTMLInputElement;
}

interface Ilist {
  value: string;
  type: name;
  msg: string;
}
interface Iadd {
  type: name;
  msg: string;
}

type name = keyof typeof rule;
const rule = {
  require(value: string, msg: string) {
    if (!value) {
      return msg;
    }
  },
  minLength(value: string, msg: string) {
    if (value.length < 6) {
      return msg;
    }
  },
  mobile(value: string, msg: string) {
    if (!/\d{11}/.test(value)) {
      return msg;
    }
  }
};
class Verification {
  private list: Ilist[] = [];
  public message: string;
  // 用函数重载的形式实现
  add<T extends { value: string }>(dom: T, type: name, msg: string): void;
  add<T extends { value: string }>(dom: T, ruleArr: Array<Iadd>): void;
  add<T extends { value: string }>(
    dom: T,
    ruleArr: Array<Iadd> | name,
    msg?: string
  ): void {
    if (Array.isArray(ruleArr)) {
      this.list.push(
        ...ruleArr.map(f => {
          return {
            value: dom.value,
            type: f.type,
            msg: f.msg
          };
        })
      );
    } else {
      this.list.push({
        value: dom.value,
        type: ruleArr,
        msg: msg
      });
    }
  }
  start(): boolean {
    for (let i = 0; i < this.list.length; i++) {
      const { value, type, msg } = this.list[i];
      const fn = rule[type](value, msg);
      if (fn) {
        this.message = msg;
        return false;
      }
    }
    this.message = "";
    return true;
  }
}

const form = document.querySelector(".cs-form") as HTMLFormElement;

form.addEventListener("submit", function(e) {
  e.preventDefault();
  const { account, password, mobile } = (form as unknown) as Iform;

  const ruleSet = new Verification();
  ruleSet.add(account, "require", "请输入账号");
  ruleSet.add(password, [
    {
      type: "require",
      msg: "请输入密码"
    },
    {
      type: "minLength",
      msg: "密码长度不符合要求"
    }
  ]);
  ruleSet.add(mobile, [
    {
      type: "require",
      msg: "请输入手机号"
    },
    {
      type: "mobile",
      msg: "请输入正确的手机号"
    }
  ]);

  if (!ruleSet.start()) {
    // 提示
    alert(ruleSet.message);
    return false;
  }
  return false;
});

撒花,这里就已经写完了,整体来看代码量还是蛮多的,主要是用 ts 写需要定义各种接口和类型,最终版本的时候我将上述的手机和账号的验证抽离成 require 函数,后续如果有用到只需要在 rule 规则中新增即可。

当然因为只是讲解策略模式的使用所以这里并没有对这个校验更完整的定义,如果有进一步需要可以将 min 和 max 属性以及 regexp 属性新增上,进一步提高校验的规则