设计模式与订单系统:状态模式管理订单状态、观察者通知订单变更、命令模式执行订单操作
订单系统,几乎是每个业务系统的核心模块。我这些年做过不少电商类的项目,从早期的单体架构到后来的微服务,订单模块的复杂度一直在变,但有些设计上的痛点始终没变——状态流转混乱、变更通知遗漏、操作逻辑耦合。
说白了,订单系统就是个天然的「设计模式试验田」。今天我就拿它开刀,把状态模式、观察者模式、命令模式揉在一起,看看怎么让代码既清晰又灵活。
一、先理清楚:三个模式分别解决什么问题?
你想想看,一个订单从创建到完成,中间要经历多少种状态?待支付、已支付、已发货、已签收、已取消……每个状态能做什么操作、不能做什么操作,规则一大堆。如果全用 if-else 硬写,代码很快就会变成一团乱麻。
我习惯这样分工:
- 状态模式:管状态流转。每个状态是一个独立类,状态之间的切换由状态类自己决定。
- 观察者模式:管变更通知。订单状态变了,要通知库存、物流、用户积分等模块。
- 命令模式:管操作执行。下单、支付、取消这些操作,封装成命令对象,方便记录日志、支持回滚。
这三个模式各管一摊,互不干扰,但又能协同工作。嗯,这里要注意:千万别把它们强行耦合在一起,保持各自独立才是关键。
二、状态模式:让订单状态自己管自己
先看状态模式。我早期做订单系统时,状态逻辑全写在 OrderService 里,一个方法几百行,改一个状态要翻半天代码。后来重构时,我把每个状态抽成独立类,世界清净了。
核心思路很简单:订单对象内部持有一个状态对象,所有状态相关的行为都委托给这个状态对象处理。
// 状态接口
public interface OrderState {
void pay(Order order);
void ship(Order order);
void confirm(Order order);
void cancel(Order order);
}
// 具体状态:待支付
public class PendingPaymentState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("支付成功,状态变为已支付");
order.setState(new PaidState());
}
@Override
public void ship(Order order) {
throw new UnsupportedOperationException("未支付不能发货");
}
@Override
public void confirm(Order order) {
throw new UnsupportedOperationException("未支付不能确认");
}
@Override
public void cancel(Order order) {
System.out.println("取消订单");
order.setState(new CancelledState());
}
}
// 具体状态:已支付
public class PaidState implements OrderState {
@Override
public void pay(Order order) {
throw new UnsupportedOperationException("已支付,不能重复支付");
}
@Override
public void ship(Order order) {
System.out.println("发货成功,状态变为已发货");
order.setState(new ShippedState());
}
@Override
public void confirm(Order order) {
throw new UnsupportedOperationException("未发货不能确认");
}
@Override
public void cancel(Order order) {
System.out.println("已支付状态下取消,需要退款");
order.setState(new CancelledState());
}
}
// 订单类
public class Order {
private OrderState state;
public Order() {
this.state = new PendingPaymentState();
}
public void setState(OrderState state) {
this.state = state;
}
public void pay() { state.pay(this); }
public void ship() { state.ship(this); }
public void confirm() { state.confirm(this); }
public void cancel() { state.cancel(this); }
}
你看,每个状态类只关心自己能做什么、不能做什么。新增一个状态,比如「退款中」,只需要加一个新类,改一下状态流转规则就行,完全不影响其他状态。
核心要点:状态模式把条件判断分散到各个状态类中,避免了大量的 if-else 或 switch-case。每个状态类只负责自己的行为逻辑,职责单一。
三、观察者模式:状态变了,通知到位
订单状态变了,不是改个字段就完事了。库存要扣减、积分要增加、物流要生成运单、用户要收到短信……这些「后续动作」如果全写在状态切换的逻辑里,那状态类就又不纯粹了。
观察者模式就是干这个的。订单状态变更时,它只管通知,具体谁响应、怎么响应,观察者自己决定。
// 观察者接口
public interface OrderObserver {
void onOrderChanged(Order order, String oldState, String newState);
}
// 具体观察者:库存服务
public class InventoryObserver implements OrderObserver {
@Override
public void onOrderChanged(Order order, String oldState, String newState) {
if ("已支付".equals(newState)) {
System.out.println("扣减库存");
}
}
}
// 具体观察者:积分服务
public class PointsObserver implements OrderObserver {
@Override
public void onOrderChanged(Order order, String oldState, String newState) {
if ("已签收".equals(newState)) {
System.out.println("赠送积分");
}
}
}
// 在 Order 类中增加观察者管理
public class Order {
private List<OrderObserver> observers = new ArrayList<>();
private OrderState state;
private String stateName = "待支付";
public void addObserver(OrderObserver observer) {
observers.add(observer);
}
public void setState(OrderState state) {
String oldState = this.stateName;
this.state = state;
this.stateName = state.getClass().getSimpleName();
notifyObservers(oldState, this.stateName);
}
private void notifyObservers(String oldState, String newState) {
for (OrderObserver observer : observers) {
observer.onOrderChanged(this, oldState, newState);
}
}
}
我曾经在一个项目中,订单状态变更后需要通知七八个下游系统。一开始用硬编码调用,每次加一个通知方都要改核心代码,后来改成观察者模式,新增通知只需要加一个 Observer 实现类,再注册进去就行。避坑指南:我曾经因为通知顺序导致过问题——库存还没扣减,物流已经生成运单了。后来加了优先级控制,才解决。
小技巧:观察者的执行顺序如果重要,可以用 PriorityQueue 或者给 Observer 加个 order 字段排序。另外,异步通知可以用线程池包装一下,避免阻塞主流程。
四、命令模式:把操作封装成对象
下单、支付、取消……这些操作如果直接写在 Service 里,日志记录、权限校验、事务控制全混在一起。命令模式可以把每个操作封装成一个命令对象,调用方只管执行,具体逻辑由命令自己负责。
// 命令接口
public interface OrderCommand {
void execute();
void undo();
}
// 具体命令:支付命令
public class PayCommand implements OrderCommand {
private Order order;
public PayCommand(Order order) {
this.order = order;
}
@Override
public void execute() {
System.out.println("记录支付日志");
order.pay();
}
@Override
public void undo() {
System.out.println("撤销支付,发起退款");
order.cancel();
}
}
// 命令调用者
public class OrderInvoker {
private List<OrderCommand> history = new ArrayList<>();
public void executeCommand(OrderCommand command) {
command.execute();
history.add(command);
}
public void undoLastCommand() {
if (!history.isEmpty()) {
OrderCommand last = history.remove(history.size() - 1);
last.undo();
}
}
}
命令模式的好处是:每个命令都是一个独立的对象,可以序列化、可以排队、可以记录日志。我在一个金融项目中,所有订单操作都通过命令模式执行,配合消息队列实现了操作的异步化和可追溯。
注意:命令的 undo 操作不是所有场景都能完美实现。比如「已发货」状态下撤销支付,你不能真的把快递追回来。所以 undo 要结合实际业务做补偿操作,而不是简单的状态回退。
五、三个模式如何协同工作?
单独看每个模式都不复杂,难的是怎么让它们配合起来。我画了一张图,帮你理清关系:
从图上可以看得很清楚:
- 命令模式在最左边,负责接收外部请求,封装成命令对象后执行。命令执行时调用 Order 的方法。
- 状态模式在中间,Order 内部持有当前状态对象,所有状态相关操作委托给状态类处理。
- 观察者模式在最右边,Order 状态变更时,自动通知所有注册的观察者。
实际调用流程是这样的:用户点击「支付」→ 前端调用 PayCommand.execute() → PayCommand 内部调用 order.pay() → order 委托给当前状态对象 PendingPaymentState.pay() → 状态对象执行支付逻辑,并将状态切换为 PaidState → Order 在 setState 时通知所有观察者 → 观察者各自执行自己的逻辑。
你看,每个模式各司其职,互不侵入。命令模式不关心状态怎么变,状态模式不关心通知谁,观察者模式不关心操作是什么。这就是设计模式组合的魅力。
六、实际项目中的一些经验
说了这么多,最后分享几点我在实战中踩过的坑:
- 状态不要太多:我见过有人把「待支付」「支付中」「支付失败」「支付成功」都做成独立状态,结果状态图复杂到没人能看懂。其实「支付中」和「支付失败」可以用一个状态加一个字段表示,别滥用状态模式。
- 观察者不要太多:一个订单变更触发十几个观察者,性能会受影响。我建议把一些非核心的观察者改成异步执行,或者用消息队列解耦。
- 命令要支持幂等:网络超时、重复调用是常态。每个命令的 execute 方法最好设计成幂等的,同一个命令执行多次和一次效果一样。
- 状态流转要有记录:我在项目中都会加一个状态变更日志表,记录每次状态变更的时间、操作人、旧状态、新状态。出了问题方便排查。
总结一下:状态模式管状态、观察者模式管通知、命令模式管操作。三个模式组合起来,订单系统的核心逻辑就清晰了。代码好维护,扩展也方便。下次你设计订单系统,不妨试试这个组合。
公众号:蓝海资料掘金营,微信deep3321