状态模式
大约 13 分钟约 3887 字
状态模式
简介
状态(State)允许对象在其内部状态改变时改变行为。对象看起来像是修改了其类。理解状态模式,有助于消除大量条件分支,将状态相关行为封装到独立类中。
状态模式的核心洞察是:当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变行为时,使用大量的 if-else 或 switch 语句会导致代码难以维护。状态模式将每种状态的行为封装到独立的类中,通过状态对象的委托来实现行为切换。
这种模式在实际业务中极为常见 —— 订单流程、审批链、工单状态、TCP 连接、自动售货机、游戏角色 AI 等都是典型的状态驱动场景。
特点
结构分析
UML 类图
+-------------------+
| IOrderState | <-- 状态接口
+-------------------+
| +Name: string |
| +Pay(order) |
| +Ship(order) |
| +Deliver(order) |
| +Cancel(order) |
| +Refund(order) |
+-------------------+
^
+-------------+-------------+-------------+
| | | |
+------------+ +------------+ +----------+ +----------+
| Pending | | Paid | | Shipped | | Delivered|
| State | | State | | State | | State |
+------------+ +------------+ +----------+ +----------+
| -> Paid | | -> Shipped | | ->Deliv | | ->Refund |
| -> Cancel | | -> Cancel | | | | |
+------------+ | -> Refund | +----------+ +----------+
+------------+
+-------------------+
| OrderContext | <-- 上下文
+-------------------+
| -State: IOrder |
| +Pay() |
| +Ship() |
| +Deliver() |
| +Cancel() |
| +Refund() |
+-------------------+状态转换图(订单流程)
+----------+ Pay +----------+ Ship +----------+
| 待付款 | -------> | 已付款 | -------> | 已发货 |
| Pending | | Paid | | Shipped |
+----------+ +----------+ +----------+
| | |
Cancel Cancel Deliver
| | |
v v v
+----------+ +----------+ +----------+
| 已取消 | | 已取消 | | 已收货 |
| Cancelled| | Cancelled| | Delivered|
+----------+ +----------+ +----------+
|
Refund
|
v
+----------+
| 已退款 |
| Refunded |
+----------+实现
订单状态机
// 状态接口
public interface IOrderState
{
string Name { get; }
void Pay(OrderContext order);
void Ship(OrderContext order);
void Deliver(OrderContext order);
void Cancel(OrderContext order);
void Refund(OrderContext order);
}
// 上下文
public class OrderContext
{
public string OrderId { get; } = Guid.NewGuid().ToString("N")[..8];
public decimal Amount { get; }
public IOrderState State { get; set; }
public OrderContext(decimal amount) { Amount = amount; State = new PendingState(); }
public void Pay() => State.Pay(this);
public void Ship() => State.Ship(this);
public void Deliver() => State.Deliver(this);
public void Cancel() => State.Cancel(this);
public void Refund() => State.Refund(this);
public void PrintStatus() => Console.WriteLine($"订单 {OrderId}: {State.Name} (¥{Amount:N2})");
}
// 各状态实现
public class PendingState : IOrderState
{
public string Name => "待付款";
public void Pay(OrderContext o) { o.State = new PaidState(); Console.WriteLine("支付成功"); }
public void Cancel(OrderContext o) { o.State = new CancelledState(); Console.WriteLine("订单已取消"); }
public void Ship(OrderContext o) => Console.WriteLine("错误: 未付款不能发货");
public void Deliver(OrderContext o) => Console.WriteLine("错误: 未付款不能确认收货");
public void Refund(OrderContext o) => Console.WriteLine("错误: 未付款不能退款");
}
public class PaidState : IOrderState
{
public string Name => "已付款";
public void Pay(OrderContext o) => Console.WriteLine("错误: 已付款");
public void Ship(OrderContext o) { o.State = new ShippedState(); Console.WriteLine("已发货"); }
public void Cancel(OrderContext o) { o.State = new CancelledState(); Console.WriteLine("已取消,退款中"); }
public void Deliver(OrderContext o) => Console.WriteLine("错误: 未发货");
public void Refund(OrderContext o) { o.State = new RefundedState(); Console.WriteLine("退款成功"); }
}
public class ShippedState : IOrderState
{
public string Name => "已发货";
public void Pay(OrderContext o) => Console.WriteLine("错误: 已付款");
public void Ship(OrderContext o) => Console.WriteLine("错误: 已发货");
public void Deliver(OrderContext o) { o.State = new DeliveredState(); Console.WriteLine("已确认收货"); }
public void Cancel(OrderContext o) => Console.WriteLine("错误: 已发货不能取消");
public void Refund(OrderContext o) => Console.WriteLine("错误: 已发货请先退货");
}
public class DeliveredState : IOrderState
{
public string Name => "已收货";
public void Pay(OrderContext o) => Console.WriteLine("错误: 已付款");
public void Ship(OrderContext o) => Console.WriteLine("错误: 已发货");
public void Deliver(OrderContext o) => Console.WriteLine("错误: 已收货");
public void Cancel(OrderContext o) => Console.WriteLine("错误: 已收货不能取消");
public void Refund(OrderContext o) { o.State = new RefundedState(); Console.WriteLine("退款申请已提交"); }
}
public class CancelledState : IOrderState
{
public string Name => "已取消";
public void Pay(OrderContext o) => Console.WriteLine("错误: 订单已取消");
public void Ship(OrderContext o) => Console.WriteLine("错误: 订单已取消");
public void Deliver(OrderContext o) => Console.WriteLine("错误: 订单已取消");
public void Cancel(OrderContext o) => Console.WriteLine("错误: 已取消");
public void Refund(OrderContext o) => Console.WriteLine("错误: 已取消");
}
public class RefundedState : IOrderState
{
public string Name => "已退款";
public void Pay(OrderContext o) => Console.WriteLine("错误: 已退款");
public void Ship(OrderContext o) => Console.WriteLine("错误: 已退款");
public void Deliver(OrderContext o) => Console.WriteLine("错误: 已退款");
public void Cancel(OrderContext o) => Console.WriteLine("错误: 已退款");
public void Refund(OrderContext o) => Console.WriteLine("错误: 已退款");
}
// 使用
var order = new OrderContext(199.99m);
order.PrintStatus(); // 待付款
order.Pay();
order.Ship();
order.Deliver();
order.PrintStatus(); // 已收货Enum 状态机(轻量实现)
public enum TcpState { Closed, Listen, SynSent, Established, FinWait }
public class TcpConnection
{
public TcpState State { get; private set; } = TcpState.Closed;
public void Open()
{
State = State switch
{
TcpState.Closed => TcpState.SynSent,
_ => throw new InvalidOperationException($"不能从 {State} 状态打开")
};
}
public void Acknowledge()
{
State = State switch
{
TcpState.SynSent => TcpState.Established,
TcpState.Listen => TcpState.Established,
_ => State
};
}
public void Close()
{
State = State switch
{
TcpState.Established => TcpState.FinWait,
TcpState.Listen => TcpState.Closed,
_ => throw new InvalidOperationException($"不能从 {State} 状态关闭")
};
}
}实战:工单状态机
在企业级应用中,工单(Ticket)系统是状态模式的经典场景。工单从创建到关闭,经历多个状态转换,每个状态下的允许操作不同。
// 工单状态枚举
public enum TicketStatus
{
Draft, // 草稿
Submitted, // 已提交
InProgress, // 处理中
PendingReview, // 待审核
Resolved, // 已解决
Closed, // 已关闭
Rejected // 已驳回
}
// 工单状态基类
public abstract class TicketStateBase
{
public abstract TicketStatus Status { get; }
// 每个状态定义自己的允许转换
public abstract void Submit(TicketContext ticket);
public abstract void StartWork(TicketContext ticket);
public abstract void CompleteWork(TicketContext ticket);
public abstract void Approve(TicketContext ticket);
public abstract void Reject(TicketContext ticket);
public abstract void Close(TicketContext ticket);
public abstract void Reopen(TicketContext ticket);
protected void Transition(TicketContext ticket, TicketStateBase newState)
{
var oldStatus = ticket.State.Status;
ticket.State = newState;
ticket.AddHistory(oldStatus, newState.Status);
Console.WriteLine($" [{oldStatus} -> {newState.Status}]");
}
}
// 各状态实现
public class DraftState : TicketStateBase
{
public override TicketStatus Status => TicketStatus.Draft;
public override void Submit(TicketContext t) => Transition(t, new SubmittedState());
public override void StartWork(TicketContext t) => LogError("草稿状态不能开始处理");
public override void CompleteWork(TicketContext t) => LogError("草稿状态不能完成");
public override void Approve(TicketContext t) => LogError("草稿状态不能审核");
public override void Reject(TicketContext t) => LogError("草稿状态不能驳回");
public override void Close(TicketContext t) => LogError("草稿状态不能关闭");
public override void Reopen(TicketContext t) => LogError("草稿状态不能重新打开");
private static void LogError(string msg) => Console.WriteLine($" 错误: {msg}");
}
public class SubmittedState : TicketStateBase
{
public override TicketStatus Status => TicketStatus.Submitted;
public override void Submit(TicketContext t) => LogError("已提交,不能重复提交");
public override void StartWork(TicketContext t) => Transition(t, new InProgressState());
public override void CompleteWork(TicketContext t) => LogError("未开始处理");
public override void Approve(TicketContext t) => LogError("不在审核环节");
public override void Reject(TicketContext t) => Transition(t, new RejectedState());
public override void Close(TicketContext t) => LogError("已提交的工单不能直接关闭");
public override void Reopen(TicketContext t) => LogError("已提交的工单不能重新打开");
}
public class InProgressState : TicketStateBase
{
public override TicketStatus Status => TicketStatus.InProgress;
public override void Submit(TicketContext t) => LogError("已在处理中");
public override void StartWork(TicketContext t) => LogError("已在处理中");
public override void CompleteWork(TicketContext t) => Transition(t, new PendingReviewState());
public override void Approve(TicketContext t) => LogError("不在审核环节");
public override void Reject(TicketContext t) => LogError("已在处理中,不能驳回");
public override void Close(TicketContext t) => LogError("处理中不能关闭");
public override void Reopen(TicketContext t) => LogError("已在处理中");
}
public class PendingReviewState : TicketStateBase
{
public override TicketStatus Status => TicketStatus.PendingReview;
public override void Submit(TicketContext t) => LogError("待审核");
public override void StartWork(TicketContext t) => Transition(t, new InProgressState()); // 打回重做
public override void CompleteWork(TicketContext t) => LogError("已完成,等待审核");
public override void Approve(TicketContext t) => Transition(t, new ResolvedState());
public override void Reject(TicketContext t) => Transition(t, new RejectedState());
public override void Close(TicketContext t) => LogError("需先审核");
public override void Reopen(TicketContext t) => LogError("需先审核");
}
public class ResolvedState : TicketStateBase
{
public override TicketStatus Status => TicketStatus.Resolved;
public override void Submit(TicketContext t) => LogError("已解决");
public override void StartWork(TicketContext t) => LogError("已解决");
public override void CompleteWork(TicketContext t) => LogError("已解决");
public override void Approve(TicketContext t) => LogError("已审核通过");
public override void Reject(TicketContext t) => Transition(t, new InProgressState());
public override void Close(TicketContext t) => Transition(t, new ClosedState());
public override void Reopen(TicketContext t) => Transition(t, new InProgressState());
}
public class ClosedState : TicketStateBase
{
public override TicketStatus Status => TicketStatus.Closed;
public override void Submit(TicketContext t) => LogError("已关闭");
public override void StartWork(TicketContext t) => Transition(t, new InProgressState()); // 重新打开
public override void CompleteWork(TicketContext t) => LogError("已关闭");
public override void Approve(TicketContext t) => LogError("已关闭");
public override void Reject(TicketContext t) => LogError("已关闭");
public override void Close(TicketContext t) => LogError("已关闭");
public override void Reopen(TicketContext t) => Transition(t, new InProgressState());
}
public class RejectedState : TicketStateBase
{
public override TicketStatus Status => TicketStatus.Rejected;
public override void Submit(TicketContext t) => Transition(t, new SubmittedState()); // 重新提交
public override void StartWork(TicketContext t) => LogError("已驳回,请重新提交");
public override void CompleteWork(TicketContext t) => LogError("已驳回");
public override void Approve(TicketContext t) => LogError("已驳回");
public override void Reject(TicketContext t) => LogError("已驳回");
public override void Close(TicketContext t) => Transition(t, new ClosedState());
public override void Reopen(TicketContext t) => Transition(t, new SubmittedState());
}
// 工单上下文
public class TicketContext
{
public string TicketId { get; } = $"TK-{Guid.NewGuid().ToString("N")[..6].ToUpper()}";
public string Title { get; init; } = "";
public TicketStateBase State { get; set; } = new DraftState();
public List<string> History { get; } = new();
public void Submit() => State.Submit(this);
public void StartWork() => State.StartWork(this);
public void CompleteWork() => State.CompleteWork(this);
public void Approve() => State.Approve(this);
public void Reject() => State.Reject(this);
public void Close() => State.Close(this);
public void Reopen() => State.Reopen(this);
public void AddHistory(TicketStatus from, TicketStatus to)
=> History.Add($"[{DateTime.UtcNow:HH:mm:ss}] {from} -> {to}");
public void PrintHistory()
{
Console.WriteLine($"工单 {TicketId}: {Title}");
Console.WriteLine($"当前状态: {State.Status}");
foreach (var h in History) Console.WriteLine($" {h}");
}
}
// 使用
var ticket = new TicketContext { Title = "服务器 CPU 使用率过高" };
ticket.Submit();
ticket.StartWork();
ticket.CompleteWork();
ticket.Approve();
ticket.Close();
ticket.PrintHistory();
// [Draft -> Submitted]
// [Submitted -> InProgress]
// [InProgress -> PendingReview]
// [PendingReview -> Resolved]
// [Resolved -> Closed]实战:基于状态表驱动的状态机
当状态和事件非常多时,为每个状态创建一个类会导致类爆炸。此时可以用状态表驱动。
public enum OrderEvent { Pay, Ship, Deliver, Cancel, Refund }
public class StateMachine<TState, TEvent> where TState : notnull where TEvent : notnull
{
private readonly Dictionary<(TState, TEvent), (TState Next, string? Error)> _transitions = new();
private readonly Dictionary<TState, List<string>> _entryActions = new();
private TState _currentState;
public TState CurrentState => _currentState;
public StateMachine(TState initialState) => _currentState = initialState;
public StateMachine<TState, TEvent> On(TState from, TEvent @event, TState to, string? error = null)
{
_transitions[(from, @event)] = (to, error);
return this;
}
public StateMachine<TState, TEvent> OnEntry(TState state, Action action)
{
if (!_entryActions.ContainsKey(state)) _entryActions[state] = new();
_entryActions[state].Add(action.Method.Name);
return this;
}
public bool TryFire(TEvent @event, out string? error)
{
if (_transitions.TryGetValue((_currentState, @event), out var transition))
{
var oldState = _currentState;
_currentState = transition.Next;
Console.WriteLine($" [{oldState} --{@event}--> {_currentState}]");
error = null;
return true;
}
error = $"在状态 {_currentState} 下不允许事件 {@event}";
Console.WriteLine($" 错误: {error}");
return false;
}
}
// 定义订单状态机
var orderMachine = new StateMachine<string, OrderEvent>("待付款")
.On("待付款", OrderEvent.Pay, "已付款")
.On("待付款", OrderEvent.Cancel, "已取消")
.On("已付款", OrderEvent.Ship, "已发货")
.On("已付款", OrderEvent.Cancel, "已取消")
.On("已付款", OrderEvent.Refund, "已退款")
.On("已发货", OrderEvent.Deliver, "已收货")
.On("已收货", OrderEvent.Refund, "已退款");
// 使用
Console.WriteLine($"当前: {orderMachine.CurrentState}");
orderMachine.TryFire(OrderEvent.Pay);
orderMachine.TryFire(OrderEvent.Ship);
orderMachine.TryFire(OrderEvent.Deliver);
orderMachine.TryFire(OrderEvent.Cancel); // 错误:已收货下不允许取消
Console.WriteLine($"最终: {orderMachine.CurrentState}");与策略模式的对比
状态模式和策略模式的结构非常相似(都是通过接口实现多态),但它们的意图不同:
状态模式 策略模式
+------------------+ +------------------+
| 状态对象可以 | | 策略对象由 |
| 自行切换状态 | | 客户端主动替换 |
| (上下文持有状态) | | (上下文持有策略) |
| 行为由状态决定 | | 行为由算法选择 |
| 关注"是什么状态" | | 关注"怎么做" |
+------------------+ +------------------+
状态模式: 客户端不知道当前状态,状态自己管理转换
策略模式: 客户端知道要做什么,主动选择策略状态模式的实现方式选择
| 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 状态类(多态) | 扩展性好、符合 OCP | 类数量多、状态间耦合 | 状态少(<10)、行为差异大 |
| Enum + switch | 简单直接、无额外类 | 违反 OCP、switch 膨胀 | 状态少(<5)、转换简单 |
| 状态表驱动 | 集中管理、易可视化 | 缺少类型安全 | 状态多、转换规则复杂 |
| 状态机框架(如 Stateless) | 功能强大、支持持久化 | 学习成本、外部依赖 | 生产级复杂状态机 |
最佳实践
- 状态转换逻辑集中管理:将所有合法转换预先定义,避免运行时出现非法状态跳转。
- 状态历史记录:记录每次状态转换的时间、操作人和原因,方便审计和回溯。
- 状态持久化:在业务系统中,状态通常需要持久化到数据库,建议只存储状态枚举值而非状态对象。
- 防止非法操作:在终态(如已取消、已关闭)下,所有操作都应返回错误而非静默忽略。
- 考虑使用现有库:对于复杂的状态机,推荐使用 Stateless 等成熟库。
优点
缺点
总结
状态模式将每个状态的行为封装为独立类,通过多态消除大量条件分支。适合订单流程、TCP 连接、工作流引擎等多状态切换场景。状态少的场景可用 Enum + switch 简化实现。注意状态间的转换逻辑可以集中在上下文中或分散在状态类中。建议在状态超过 3 个且行为差异大时使用状态模式。
状态模式的本质价值在于:当业务对象有大量状态驱动的行为时,将"在什么状态下能做什么"这个决策从臃肿的条件分支中提取出来,交给状态对象自己管理。这不仅让代码更清晰,也使得新增状态时不需要修改已有代码。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
- 前端主题最好同时看浏览器原理、框架机制和工程化约束。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
- 对关键页面先建立状态流和数据流,再考虑组件拆分。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
- 只追框架新特性,不分析实际渲染成本。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
- 继续补齐设计系统、SSR/边缘渲染、监控告警和组件库治理。
适用场景
- 当你准备把《状态模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
复盘问题
- 如果把《状态模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《状态模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《状态模式》最大的收益和代价分别是什么?
