state模式不是为简化嵌套,而是解决状态逻辑错位:将状态变化规则与业务动作分离,通过状态类封装“能做什么”和“能变为什么”,避免if分支散落、职责不清。

为什么if嵌套不是“写得不够简洁”,而是状态逻辑错位
PHP里层层缩进的 if 块(比如订单状态流转中反复判断 $order->status === 'pending'、=== 'paid'、=== 'shipped')本质不是结构问题,而是把「状态变化规则」和「业务动作」混在了一起。你改一个条件,要翻三处;加一个新状态,要补五六个 if 分支——这不是代码丑,是职责没分清。
State 模式不是为炫技,是把「当前能做什么」「下一步能变成什么」这两件事,从流程控制里拎出来,单独封装。
用State类替代if分支:每个状态一个类,只管自己能响应什么
别再让主流程决定「现在该执行哪段逻辑」,而是让当前状态对象自己说「我能处理 pay() 吗?能,我就干;不能,我就抛异常或静默忽略」。
- 定义统一接口
OrderStateInterface,声明pay()、ship()、cancel()等方法 - 为每种状态建类:
PendingState、PaidState、ShippedState,各自实现接口 -
PendingState::pay()改变订单状态为PaidState并返回新实例;PaidState::pay()则直接 throw new LogicException('Already paid') - 订单实体只持有一个
$state属性,类型是OrderStateInterface,所有操作委托给它
这样新增「退货中」状态,只需加一个 ReturningState 类,不碰原有 if 也不改订单主类。
避免State模式踩坑:状态切换必须可控,不能靠new随意创建
常见错误是让客户端代码直接 new PaidState($order),绕过状态流转约束。状态切换必须收口到状态类内部或专用工厂。
- 状态类构造函数设为
private或protected,禁止外部实例化 - 只允许通过「合法动作」触发切换:比如
PendingState::pay()返回new PaidState($this->order),且该方法内部校验支付是否有效 - 订单主类的
pay()方法只做一层代理:$this->state = $this->state->pay();,不参与判断逻辑 - 若需强制重置状态(如测试),提供受控的
forceSetState(OrderStateInterface $state),并打日志或 require debug mode
否则状态对象就退化成一堆松散的工具类,和原来 if 嵌套一样难维护。
什么时候不该上State模式?警惕过度设计信号
State 模式适合状态多、流转规则复杂、且状态行为差异大的场景。如果只有两三个状态,每个状态只有一两个动作,硬套反而增加理解成本。
- 状态数 ≤ 3,且每个状态下可执行的动作完全相同 → 直接用
switch ($order->status)更轻量 - 状态之间没有明确流转约束(比如「已取消」后还能「重新支付」)→ State 模式隐含的「单向/有限转移」假设会失效
- 项目用的是 Laravel,已有
spatie/laravel-state-machine这类成熟包 → 优先封装现成方案,别重复造轮子 - 团队 PHP 版本低于 7.4,无法用属性类型声明 →
$state缺少类型提示,容易误传非状态对象,调试成本陡增
真正卡住维护效率的,从来不是嵌套层数,而是谁该对「此时能不能做这件事」负责。State 模式把责任还给状态本身,但前提是状态边界得划清楚——模糊的状态定义,比深嵌套更难修。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











