从表单校验到订单流转,彻底搞懂前端开发中的策略模式与状态模式

策略模式和状态模式是前端开发中最容易混淆的两个设计模式。它们的实现方式高度相似------都用对象映射替代条件分支,都通过委托来分发行为。但策略模式像你拿着遥控器手动换台,状态模式像空调根据室温自动调温。本质上,一个是外部选择行为,一个是内部自动切换。

本文从表单校验和订单状态流转两个真实场景出发,分别用策略模式和状态模式重构代码,深度对比两者的本质区别。

一、表单校验------策略模式的用武之地

在一个后台管理系统中,表单字段需要支持多种校验规则:必填、邮箱格式、手机号、最小长度等。最初的代码往往是这样的:

javascript 复制代码
function validate(value, type) {
  if (type === 'required') {
    return value && value.trim() ? '' : '必填';
  } else if (type === 'email') {
    return /^\S+@\S+\.\S+$/.test(value) ? '' : '邮箱格式错误';
  } else if (type === 'phone') {
    return /^1[3-9]\d{9}$/.test(value) ? '' : '手机号格式错误';
  } else if (type === 'minLength') {
    return value.length >= 6 ? '' : '至少6位';
  }
  return '';
}

这段代码的问题很明显:新增一种校验规则(比如身份证号),就要改动这个函数。随着业务发展,这个函数会越来越长,每次改动都可能影响已有逻辑。

策略模式的核心思路:把每种校验规则封装成独立的纯函数,用一个配置对象统一管理。

javascript 复制代码
// 每种校验规则是一个独立的策略函数
const validators = {
  required: (val) => (val && val.trim() ? '' : '必填'),
  email: (val) => (/^\S+@\S+\.\S+$/.test(val) ? '' : '邮箱格式错误'),
  phone: (val) => (/^1[3-9]\d{9}$/.test(val) ? '' : '手机号格式错误'),
  minLength: (val) => (val.length >= 6 ? '' : '至少6位'),
};

// 通用校验器:根据规则名选择对应的策略执行
function validate(value, rules) {
  for (const rule of rules) {
    const fn = typeof rule === 'function' ? rule : validators[rule];
    const error = fn(value);
    if (error) return error;
  }
  return '';
}

// 使用:调用方主动选择校验规则
validate('hello@test.com', ['required', 'email']);  // ''
validate('hi', ['required', 'email']);               // '邮箱格式错误'

注意关键特征 :调用方通过传入规则名('required''email'),主动选择 用哪个校验函数。这些校验函数之间互不依赖、完全平行------required 不需要知道 email 的存在,也不需要知道当前是在校验哪个表单。

这就是策略模式的核心价值:用配置对象替代条件分支,调用方主动选择算法,各算法互相独立,对扩展开放。

二、订单状态流转------状态模式的用武之地

再看另一个场景。电商系统中,订单从创建到完成会经历多个状态:待支付 → 已支付 → 已发货 → 已完成。每个状态下,同一个操作(如"支付")的行为完全不同------待支付时可以支付,已支付时应该提示"请勿重复支付",已发货时根本不应该有支付按钮。

最直接的写法是在每个操作方法里用 if-else 判断当前状态:

javascript 复制代码
class Order {
  constructor() {
    this.status = 'pending';
  }

  pay() {
    if (this.status === 'pending') {
      console.log('支付成功,更新为已支付');
      this.status = 'paid';
    } else if (this.status === 'paid') {
      console.log('订单已支付,请勿重复支付');
    } else if (this.status === 'shipped') {
      console.log('已发货订单不能支付');
    }
  }

  ship() {
    if (this.status === 'paid') {
      console.log('已发货');
      this.status = 'shipped';
    } else if (this.status === 'pending') {
      console.log('待支付订单不能发货');
    }
  }
}

状态一多,每个方法里都塞满了 if-else,新增一个状态(如"退款中")就要改所有相关方法。

状态模式的核心思路:将每种状态封装成独立的状态类,每个状态类内部定义了该状态下的所有行为。订单对象只负责调用当前状态的方法,行为自动根据状态变化。

javascript 复制代码
// 每个状态封装成独立的类
class PendingState {
  pay(order) {
    console.log('支付成功,更新为已支付');
    order.setState(new PaidState());  // 状态自己决定下一步转换到哪
  }
  ship() { throw new Error('待支付订单不能发货'); }
  complete() { throw new Error('待支付订单不能完成'); }
}

class PaidState {
  pay() { throw new Error('订单已支付,请勿重复支付'); }
  ship(order) {
    console.log('已发货');
    order.setState(new ShippedState());
  }
  complete() { throw new Error('请先发货'); }
}

class ShippedState {
  pay() { throw new Error('已发货订单不能支付'); }
  ship() { throw new Error('已发货订单不能重复发货'); }
  complete(order) {
    console.log('订单已完成');
    order.setState(new CompletedState());
  }
}

class CompletedState {
  pay() { throw new Error('已完成订单不能支付'); }
  ship() { throw new Error('已完成订单不能发货'); }
  complete() { throw new Error('订单已完成'); }
}

// 订单类:只负责委托给当前状态
class Order {
  constructor() {
    this.state = new PendingState();
  }
  setState(state) { this.state = state; }
  pay() { this.state.pay(this); }      // 行为由当前状态决定
  ship() { this.state.ship(this); }
  complete() { this.state.complete(this); }
}

注意关键特征 :调用方只调用 order.pay()不需要知道订单当前是什么状态 。订单对象内部自己根据状态决定行为------待支付时执行支付逻辑,已支付时抛出错误。状态的转换也由状态类自己管理(PendingState.pay 内部调用 order.setState(new PaidState()))。

这就是状态模式的核心价值:对象根据内部状态自动改变行为,状态转换被封装在各个状态类中,调用方无需感知当前状态。

三、核心区别:"谁在选择"

回到最初的问题:策略模式和状态模式到底有什么区别?它们都用对象映射消灭了 if-else,代码结构甚至看起来差不多。

最根本的区别在于"选择权在谁手中"

对比维度 策略模式 状态模式
选择权 调用方主动选择用哪个策略 对象内部根据状态自动决定
调用方式 validators['email'](value) ------ 外部指定算法名 order.pay() ------ 外部不知道内部状态
函数间关系 各策略函数互不依赖,完全平行 状态行为可能相互调用,存在层级关系
典型场景 表单校验、价格计算、排序算法 订单流转、登录态、播放器状态
新增扩展 加一个策略函数 加一个状态类

一个最直观的判断方法

  • 如果你发现调用方在说"用这个算法来处理这个数据",那是策略模式------外部主动选择。
  • 如果你发现调用方在说"做这件事",然后对象自己决定怎么做,那是状态模式------内部自动变化。

四、总结

特征 策略模式 状态模式
谁在选 调用方选 对象自己变
函数间关系 平行独立 可能互相调用
调用方是否感知当前状态 是(需要传入 tag) 否(只调用方法)
典型场景 表单校验、价格计算 订单流转、登录态
一句话 "我给你选项,你选一个" "我根据自身状态,自己决定怎么做"

下次当你面对一堆 if-else 时,先问自己一个问题:"是调用方在主动选择不同算法,还是对象自己在不同状态下表现出不同行为?" 答案会直接告诉你该用策略模式还是状态模式。

相关推荐
东风破_15 分钟前
React 自定义 Hook 实战:封装鼠标坐标与副作用清理
前端
bonechips16 分钟前
useRef + Web Worker:React 中的多线程计算
前端·javascript
Sterting17 分钟前
第 10 节:异步请求与 Axios —— 前端与后端
前端
Coder_Ke20 分钟前
源码没错,API 却“失踪”了:微信开发者工具的一次作用域背刺
前端
渣波25 分钟前
从“迷路”到“瞬移”:彻底搞懂 React Router 的 `useLocation` 与 `useNavigate`
前端·javascript
申君健248641827720225 分钟前
WebGPU 开发入门:从设备初始化到渲染一帧
前端
橘子星26 分钟前
React Context + 自定义 Hooks:告别 Props 地狱,一行代码跨层级传数据
前端·javascript
moMo26 分钟前
前端路由,其实就是三个对象在演戏
前端·react.js
用户29307509766927 分钟前
React 自定义 Hooks 实战:用 useMouse 理解响应式封装
前端
触底反弹29 分钟前
🔥 别再只用 useRef 拿 DOM 了!这 5 个实战场景让你彻底理解它(附源码解析 + 面试题)
前端·javascript·react.js