综合实战:用设计模式重构遗留电商订单系统

说实话,我接手过不少遗留系统。每次打开那些几千行的「上帝类」,心里都咯噔一下。今天要聊的这个电商订单系统,就是典型代表——一个 OrderService 类,足足 3000 行,if-else 嵌套了 8 层。嗯,是时候用设计模式给它「刮骨疗毒」了。

系统现状:一团乱麻

先说说这个系统长什么样。它处理订单的完整生命周期:创建、支付、发货、确认收货、退款、取消。每个环节都有不同的业务逻辑,而且还在不断叠加新需求。

我接手时,代码大概是这样的:

public class OrderService {
    public void processOrder(Order order, String action) {
        if ("CREATE".equals(action)) {
            // 50行创建逻辑
        } else if ("PAY".equals(action)) {
            // 80行支付逻辑
        } else if ("SHIP".equals(action)) {
            // 60行发货逻辑
        }
        // 还有十几个 else if...
    }
}

你想想看,每次加一个新动作,就得往这个类里塞代码。测试越来越慢,改一个地方可能影响十个功能。这哪是维护代码,分明是在拆炸弹。

重构目标:让代码「活」起来

我的目标很明确:每个模式解决一类问题,让代码可扩展、可测试、可维护。具体来说,我打算用这些模式:

设计模式 解决的问题 应用场景
单例模式 全局唯一实例 配置管理器、日志记录器
工厂模式 对象创建逻辑 订单处理器创建
策略模式 算法族封装 支付方式、配送方式
状态模式 状态流转 订单状态机
观察者模式 事件通知 订单状态变更通知
代理模式 访问控制 权限校验、日志记录

第一步:用单例模式管理配置

我先从最简单的开始。系统里有个配置管理器,负责读取订单相关的配置参数。以前每次用到配置都 new 一个实例,数据库连接开了又关,关了又开。我习惯用枚举实现单例,简洁又线程安全。

public enum OrderConfigManager {
    INSTANCE;

    private Properties props;

    OrderConfigManager() {
        props = new Properties();
        // 加载配置文件
        loadConfig();
    }

    public String getConfig(String key) {
        return props.getProperty(key);
    }
}

这样写的好处是:全局只有一个配置实例,不会重复加载。我在项目中遇到过因为配置重复加载导致的性能问题,后来用单例模式一把就解决了。

第二步:用工厂模式创建订单处理器

接下来处理那个巨无霸的 if-else。我决定把每个订单动作拆成独立的处理器,然后用工厂来创建它们。

// 策略接口
public interface OrderHandler {
    void handle(Order order);
}

// 具体处理器
public class CreateOrderHandler implements OrderHandler {
    public void handle(Order order) {
        // 创建订单逻辑
    }
}

public class PayOrderHandler implements OrderHandler {
    public void handle(Order order) {
        // 支付逻辑
    }
}

// 工厂类
public class OrderHandlerFactory {
    private static Map<String, OrderHandler> handlers = new HashMap<>();

    static {
        handlers.put("CREATE", new CreateOrderHandler());
        handlers.put("PAY", new PayOrderHandler());
        // 注册更多处理器
    }

    public static OrderHandler getHandler(String action) {
        return handlers.get(action);
    }
}

这样一来,主流程就变成了:

OrderHandler handler = OrderHandlerFactory.getHandler(action);
handler.handle(order);

干净利落。加新动作?只需要新增一个处理器类,然后在工厂里注册一下。不用改任何现有代码。

我的经验:工厂模式特别适合「创建逻辑复杂」或「需要集中管理对象创建」的场景。但别滥用,如果只有两三个对象,直接 new 反而更清晰。

第三步:用状态模式管理订单状态机

订单状态流转是这个系统最复杂的部分。以前的状态判断散落在各个方法里,经常出现「订单已发货还能取消」这种 bug。我决定用状态模式来重构。

先画个状态图:

待支付 已支付 已发货 已完成 已取消 支付 发货 确认 取消 取消

状态模式的精髓在于:每个状态都是一个独立类,状态之间的转换由状态类自己决定

// 状态接口
public interface OrderState {
    void pay(OrderContext context);
    void ship(OrderContext context);
    void confirm(OrderContext context);
    void cancel(OrderContext context);
}

// 具体状态:待支付
public class PendingPaymentState implements OrderState {
    public void pay(OrderContext context) {
        System.out.println("支付成功");
        context.setState(new PaidState());
    }

    public void ship(OrderContext context) {
        throw new IllegalStateException("未支付不能发货");
    }

    public void confirm(OrderContext context) {
        throw new IllegalStateException("未支付不能确认");
    }

    public void cancel(OrderContext context) {
        System.out.println("取消订单");
        context.setState(new CancelledState());
    }
}

// 上下文
public class OrderContext {
    private OrderState state;

    public OrderContext() {
        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); }
}
我曾经踩过的坑:状态模式容易把状态类写得「胖」起来。我的建议是,每个状态类只关注「自己能做什么」,不要越界处理其他状态的逻辑。如果状态转换逻辑复杂,可以引入状态机引擎来管理。

第四步:用观察者模式处理事件通知

订单状态变了,需要通知库存系统、物流系统、用户通知系统。以前的做法是在每个状态变更的地方硬编码调用。我改用观察者模式,让订单状态变更时自动通知所有订阅者。

// 事件接口
public interface OrderEvent {
    String getType();
    Order getOrder();
}

// 观察者接口
public interface OrderObserver {
    void onEvent(OrderEvent event);
}

// 被观察者
public class OrderEventPublisher {
    private List<OrderObserver> observers = new ArrayList<>();

    public void subscribe(OrderObserver observer) {
        observers.add(observer);
    }

    public void publish(OrderEvent event) {
        for (OrderObserver observer : observers) {
            observer.onEvent(event);
        }
    }
}

// 具体观察者
public class InventoryObserver implements OrderObserver {
    public void onEvent(OrderEvent event) {
        if ("PAY".equals(event.getType())) {
            // 扣减库存
        }
    }
}

public class NotificationObserver implements OrderObserver {
    public void onEvent(OrderEvent event) {
        // 发送通知
    }
}

这样设计的好处是:订单模块不需要知道谁在监听,新增通知渠道也不用改订单代码。我在项目中用这种方式对接过短信、邮件、站内信三个通知渠道,后来加微信通知,一行订单代码都没改。

第五步:用代理模式做权限控制

最后,有些操作需要权限校验。比如只有管理员才能取消已发货的订单。我不想把权限逻辑混进业务代码里,于是用代理模式包了一层。

public class OrderServiceProxy implements OrderService {
    private OrderService realService;
    private UserContext userContext;

    public OrderServiceProxy(OrderService realService, UserContext userContext) {
        this.realService = realService;
        this.userContext = userContext;
    }

    public void cancelOrder(Order order) {
        if (!userContext.isAdmin()) {
            throw new SecurityException("只有管理员才能取消订单");
        }
        realService.cancelOrder(order);
    }
}

代理模式说白了就是「中间人」。它可以在不改变原有代码的情况下,增加额外的控制逻辑。我习惯用它来做:权限校验、日志记录、性能监控。

重构后的架构总览

来,看看重构后的整体架构:

订单入口 代理层(权限/日志) 工厂模式(处理器创建) 状态模式(状态流转) 策略模式(支付/配送) 单例模式(配置管理) 观察者模式(事件通知) 入口 代理 核心 基础

重构效果:数字说话

重构完成后,我统计了一下变化:

  • 代码行数:从 3000 行降到 1200 行(减少了 60%)
  • 类数量:从 5 个增加到 18 个(每个类职责更单一)
  • 测试覆盖率:从 20% 提升到 85%
  • 新增功能耗时:从 3 天缩短到 0.5 天

核心收获:设计模式不是银弹,但它是解决特定问题的「最佳实践」。这次重构让我深刻体会到:好的架构是「生长」出来的,不是「设计」出来的。先让代码跑起来,再逐步用模式优化,这才是务实的做法。

说实话,重构遗留系统就像给飞行中的飞机换引擎。但只要你选对了模式,每一步都稳扎稳打,结果会让你惊喜。嗯,这就是我这次实战的全部心得了。


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