策略模式和状态模式是前端开发中最容易混淆的两个设计模式。它们的实现方式高度相似------都用对象映射替代条件分支,都通过委托来分发行为。但策略模式像你拿着遥控器手动换台,状态模式像空调根据室温自动调温。本质上,一个是外部选择行为,一个是内部自动切换。
本文从表单校验和订单状态流转两个真实场景出发,分别用策略模式和状态模式重构代码,深度对比两者的本质区别。

一、表单校验------策略模式的用武之地
在一个后台管理系统中,表单字段需要支持多种校验规则:必填、邮箱格式、手机号、最小长度等。最初的代码往往是这样的:
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 时,先问自己一个问题:"是调用方在主动选择不同算法,还是对象自己在不同状态下表现出不同行为?" 答案会直接告诉你该用策略模式还是状态模式。