设计模式与订单系统:状态模式管理订单状态、观察者通知订单变更、命令模式执行订单操作

订单系统,几乎是每个业务系统的核心模块。我这些年做过不少电商类的项目,从早期的单体架构到后来的微服务,订单模块的复杂度一直在变,但有些设计上的痛点始终没变——状态流转混乱、变更通知遗漏、操作逻辑耦合。

说白了,订单系统就是个天然的「设计模式试验田」。今天我就拿它开刀,把状态模式、观察者模式、命令模式揉在一起,看看怎么让代码既清晰又灵活。

一、先理清楚:三个模式分别解决什么问题?

你想想看,一个订单从创建到完成,中间要经历多少种状态?待支付、已支付、已发货、已签收、已取消……每个状态能做什么操作、不能做什么操作,规则一大堆。如果全用 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 要结合实际业务做补偿操作,而不是简单的状态回退。

五、三个模式如何协同工作?

单独看每个模式都不复杂,难的是怎么让它们配合起来。我画了一张图,帮你理清关系:

订单系统三种设计模式协同架构 命令模式 封装操作逻辑 PayCommand CancelCommand ShipCommand 每个命令持有 Order 引用 执行时调用 Order 的方法 状态模式 管理状态流转 PendingPaymentState PaidState ShippedState CancelledState Order 持有当前状态对象 状态切换由状态类自己决定 观察者模式 通知变更事件 InventoryObserver PointsObserver LogisticsObserver SmsObserver 观察者注册到 Order 状态变更时自动通知 调用 通知

从图上可以看得很清楚:

  • 命令模式在最左边,负责接收外部请求,封装成命令对象后执行。命令执行时调用 Order 的方法。
  • 状态模式在中间,Order 内部持有当前状态对象,所有状态相关操作委托给状态类处理。
  • 观察者模式在最右边,Order 状态变更时,自动通知所有注册的观察者。

实际调用流程是这样的:用户点击「支付」→ 前端调用 PayCommand.execute() → PayCommand 内部调用 order.pay() → order 委托给当前状态对象 PendingPaymentState.pay() → 状态对象执行支付逻辑,并将状态切换为 PaidState → Order 在 setState 时通知所有观察者 → 观察者各自执行自己的逻辑。

你看,每个模式各司其职,互不侵入。命令模式不关心状态怎么变,状态模式不关心通知谁,观察者模式不关心操作是什么。这就是设计模式组合的魅力。

六、实际项目中的一些经验

说了这么多,最后分享几点我在实战中踩过的坑:

  • 状态不要太多:我见过有人把「待支付」「支付中」「支付失败」「支付成功」都做成独立状态,结果状态图复杂到没人能看懂。其实「支付中」和「支付失败」可以用一个状态加一个字段表示,别滥用状态模式。
  • 观察者不要太多:一个订单变更触发十几个观察者,性能会受影响。我建议把一些非核心的观察者改成异步执行,或者用消息队列解耦。
  • 命令要支持幂等:网络超时、重复调用是常态。每个命令的 execute 方法最好设计成幂等的,同一个命令执行多次和一次效果一样。
  • 状态流转要有记录:我在项目中都会加一个状态变更日志表,记录每次状态变更的时间、操作人、旧状态、新状态。出了问题方便排查。

总结一下:状态模式管状态、观察者模式管通知、命令模式管操作。三个模式组合起来,订单系统的核心逻辑就清晰了。代码好维护,扩展也方便。下次你设计订单系统,不妨试试这个组合。

公众号:蓝海资料掘金营,微信deep3321