状态模式的核心是状态变更的边界控制,通过抽象状态类和具体状态类封装业务规则,强制状态切换走changeState()方法并校验合法性,避免直接修改字段导致副作用遗漏。

状态模式的核心不是类图,是状态变更的边界控制
PHP里用状态模式管用户账号激活/禁用,本质是把 is_active 这种布尔字段背后隐含的业务规则显式化。直接改数据库字段容易漏掉副作用(比如禁用时要清空 session、发通知、冻结关联订单),而状态模式强制你把“从激活变成禁用”这件事封装成一个动作,而不是散落在各处的 $user->active = false。
必须定义抽象状态类和具体状态类
不能只靠一个 User 类加一堆 if-else 判断当前状态再执行逻辑——那样只是状态判断,不是状态模式。关键结构是:
-
User类持有一个State接口实例,不保存active布尔值 -
ActiveState和InactiveState都实现State接口,各自实现activate()、deactivate()、login()等行为 - 状态切换由具体状态类自己决定:比如
ActiveState::deactivate()会调用$user->changeState(new InactiveState())
示例片段:
interface State
{
public function activate(User $user);
public function deactivate(User $user);
public function login(User $user): bool;
}
class ActiveState implements State
{
public function deactivate(User $user): void
{
// 清 session、发禁用通知、冻结订单...
$user->changeState(new InactiveState());
}
public function activate(User $user): void
{
// 已激活,什么也不做,或抛异常
}
public function login(User $user): bool
{
return true;
}
}
状态切换必须走 changeState() 方法,不能直接 new
常见错误是业务代码里直接 $user->state = new InactiveState(),这绕过了状态类自身的约束逻辑。正确做法是所有状态变更都通过 User::changeState(State $state),并在该方法里做校验(比如禁止从 InactiveState 切回 ActiveState 除非满足特定条件)。
- 在
changeState()里记录状态变更日志(时间、操作人、原因) - 检查是否允许切换:例如
InactiveState的activate()可能要求$user->ban_reason === null - 避免循环依赖:状态类不要持有
User实例的引用,改用传参方式(如上面示例),否则 GC 不友好
数据库字段仍需保留,但只作快照,不参与逻辑判断
别删掉数据库里的 is_active 字段——它对查询、索引、管理后台列表页渲染仍然必要。但 PHP 业务逻辑中,所有“是否可登录”“能否下单”等判断,必须调用 $user->state->login() 或 $user->state->canPlaceOrder(),而不是读 $user->is_active。
- 每次状态变更后,同步更新数据库字段,但仅作为最终一致性快照
- 如果使用 Laravel,可在
changeState()末尾触发$user->save(),但不要让 Eloquent 的 accessor/mutator 干预状态逻辑 - 注意缓存:用户状态变了,得主动删掉 Redis 里
user:123:state这类键,否则下次读缓存还是旧状态
最易被忽略的是:状态类本身不该有持久化逻辑,也不该碰 DB 或 cache。它只回答“此刻能做什么”,不负责“做完之后存哪”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











