状态切换必须由当前状态对象自己触发,通过handleEvent()决定合法流转并调用order.setState()完成切换,禁止外部直接赋值;取消等操作应进入独立终态而非反向跳转;使用std::unique_ptr管理状态以确保安全析构。

状态切换必须由当前状态对象自己触发
很多人写状态模式时,把状态变更逻辑放在 Order 类里,比如加个 setState() 方法让外部直接赋值——这会破坏封装,导致状态非法跳转(比如从 Shipped 直接切回 PendingPayment)。正确做法是:每个具体状态类(如 PendingPaymentState)内部决定「下一步能变成什么」,并通过回调通知上下文。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 在抽象基类
OrderState中定义纯虚函数handleEvent(Order&),不暴露 setter - 每个子类在处理完事件后,调用
order.setState(std::make_unique<NextState>())完成切换 - 避免在状态类中持有
Order*原始指针;改用引用或弱持有(如std::weak_ptr<Order>),防止循环引用 - 状态构造函数不接收业务参数(如金额、物流单号),这些应由
Order自己维护并传入handleEvent
订单取消和异常回滚不能靠“反向状态”硬编码
常见错误是为每个正向流转(Pending → Confirmed → Shipped)配一个反向状态(Shipped → Confirmed → Pending),结果导致状态图爆炸、逻辑耦合。订单取消本质不是状态倒退,而是进入独立的终态(Canceled),且需检查前置条件(比如已发货就不能取消)。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 取消操作统一走
cancel()事件,由当前状态自行判断是否允许:PendingPaymentState::handleCancel()可直接切到CanceledState;ShippedState::handleCancel()则抛出异常或返回失败码 - 若需补偿操作(如退款),不要在状态类里写退款逻辑,而是通过观察者或回调通知
Order执行refund(),保持状态类只管流程,不管业务副作用 - 所有终态(
Canceled、Completed、Expired)都应重载handleEvent()并直接忽略后续事件,避免误触发
std::unique_ptr<OrderState> 是最安全的状态存储方式
用裸指针或 std::shared_ptr 管理状态对象容易引发内存泄漏或意外共享。裸指针无法自动析构,shared_ptr 可能因循环引用导致状态对象永不释放(尤其当状态又持有 Order 引用时)。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 在
Order类中声明std::unique_ptr<OrderState> state_,构造时用std::make_unique<PendingPaymentState>(*this)初始化 - 状态切换时直接移动赋值:
state_ = std::make_unique<ConfirmedState>(*this),旧状态自动析构 - 如果需要在状态中调用
Order的非 const 成员函数,传入Order&引用比传指针更明确所有权边界 - 禁止在状态类中对
state_做reset()或release()操作——这该是Order自己的事
测试状态流转必须覆盖非法跳转和边界条件
单元测试只验证「A→B→C」是不够的。真实系统中,前端可能并发点击「支付」和「取消」,网络超时导致重复提交,或者数据库更新失败但状态已变更——这些都会让状态机进入不可预期路径。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
GTEST写测试时,对每个状态类单独测其handleEvent行为,而非只测Order接口 - 显式构造非法跳转场景:比如让
ShippedState处理confirmPayment(),断言是否抛出std::logic_error或返回错误码 - 模拟部分失败:在
ConfirmedState::handleShip()中手动抛异常,验证状态是否仍停留在Confirmed而非半途卡在中间态 - 用
std::atomic_bool模拟并发调用,检查状态指针是否被多线程同时修改(虽不推荐在状态机内做锁,但测试得提前暴露问题)
状态模式真正难的不是写几个类,而是厘清哪些决策属于状态,哪些属于上下文,以及怎么让非法流转在编译期或运行初期就暴露出来。别急着堆子类,先画清楚状态迁移图,再决定每个箭头该由谁来画。


















