28、设计模式综合案例(上):电商系统中的设计模式应用

讲到这里,设计模式的核心知识点基本都过了一遍。但我知道,很多同学学完理论后最头疼的问题是——这些模式到底怎么组合着用?

我个人习惯,在讲综合案例时,会先搭一个业务场景。今天我们就拿电商系统来开刀。为什么选电商?因为电商的业务足够复杂,几乎涵盖了所有主流设计模式的应用场景。你想想看,从用户下单、库存扣减、支付处理、到订单状态流转,每一步都有模式的身影。

核心观点:设计模式不是孤立存在的。在一个成熟的电商系统中,往往是多个模式协同工作,形成一套完整的架构方案。

电商系统的核心业务链路

我们先梳理一下电商系统中最核心的一条链路:用户下单 -> 库存校验 -> 支付处理 -> 订单状态更新。这条链路看似简单,但每个环节都有不少坑。

我在项目中遇到过这样一个问题:双十一大促时,库存扣减和订单创建之间出现了数据不一致,导致超卖。后来怎么解决的?其实就是用设计模式把各个模块解耦,让它们各司其职。

下面这张图,是我梳理的电商核心模块与设计模式的对应关系:

电商核心模块与设计模式映射 用户下单 工厂模式 + 建造者模式 库存校验 策略模式 + 责任链模式 支付处理 策略模式 + 模板方法 订单状态管理 状态模式 + 观察者模式 通知服务 观察者模式 + 适配器模式 日志审计 代理模式 + 装饰器模式 核心思想:每个模块只关注自己的职责,通过接口进行通信 设计原则贯穿始终 单一职责 | 开闭原则 | 依赖倒置 | 接口隔离 | 里氏替换

订单创建:工厂模式 + 建造者模式

先看订单创建这个环节。电商的订单类型五花八门:普通订单、秒杀订单、拼团订单、预售订单……如果每个订单都写一套创建逻辑,代码会膨胀到没法维护。

我一般会这样设计:用工厂模式来创建不同类型的订单对象,用建造者模式来组装订单的复杂属性。

// 订单工厂:根据类型创建不同订单
public class OrderFactory {
    public static Order createOrder(String type) {
        switch (type) {
            case "NORMAL":
                return new NormalOrder();
            case "FLASH_SALE":
                return new FlashSaleOrder();
            case "GROUP_BUY":
                return new GroupBuyOrder();
            default:
                throw new IllegalArgumentException("未知订单类型");
        }
    }
}

// 建造者模式:组装订单的复杂属性
public class OrderBuilder {
    private Order order;
    
    public OrderBuilder(String type) {
        this.order = OrderFactory.createOrder(type);
    }
    
    public OrderBuilder withUserId(Long userId) {
        order.setUserId(userId);
        return this;
    }
    
    public OrderBuilder withItems(List<OrderItem> items) {
        order.setItems(items);
        return this;
    }
    
    public OrderBuilder withAddress(Address address) {
        order.setAddress(address);
        return this;
    }
    
    public Order build() {
        // 校验必填字段
        if (order.getUserId() == null) {
            throw new IllegalStateException("用户ID不能为空");
        }
        return order;
    }
}

我的经验:工厂模式解决了「创建谁」的问题,建造者模式解决了「怎么创建」的问题。两者搭配,代码既清晰又灵活。我曾经在一个项目中看到有人用10个if-else来创建订单,后来重构时改成工厂+建造者,代码量减少了40%。

库存扣减:策略模式 + 责任链模式

库存扣减是电商系统的核心难点。不同的商品类型,扣减策略完全不同:普通商品直接扣库存,预售商品先扣预售库存再转普通库存,虚拟商品不扣库存只扣次数……

这里我推荐用策略模式来封装不同的扣减算法。同时,扣减前需要做一系列校验:商品是否存在、库存是否充足、是否在活动期内……这些校验用责任链模式来处理最合适。

// 库存扣减策略接口
public interface InventoryDeductionStrategy {
    boolean deduct(Product product, int quantity);
}

// 普通商品扣减策略
public class NormalProductDeduction implements InventoryDeductionStrategy {
    @Override
    public boolean deduct(Product product, int quantity) {
        if (product.getStock() < quantity) {
            return false;
        }
        product.setStock(product.getStock() - quantity);
        return true;
    }
}

// 责任链:扣减前的校验链
public abstract class DeductionHandler {
    protected DeductionHandler next;
    
    public void setNext(DeductionHandler next) {
        this.next = next;
    }
    
    public abstract boolean handle(Product product, int quantity);
}

public class StockCheckHandler extends DeductionHandler {
    @Override
    public boolean handle(Product product, int quantity) {
        if (product.getStock() < quantity) {
            throw new RuntimeException("库存不足");
        }
        return next == null || next.handle(product, quantity);
    }
}

注意:库存扣减一定要保证原子性。在高并发场景下,建议用Redis的Lua脚本或者数据库的乐观锁来实现。我曾经见过一个项目,扣库存时先查再扣,结果并发请求导致超卖,赔了不少钱。

支付处理:策略模式 + 模板方法

支付环节,说白了就是对接各种第三方支付渠道:微信支付、支付宝、银联、甚至国外的Stripe。每个渠道的接口参数、签名方式、回调格式都不一样。

我的做法是:用策略模式封装不同支付渠道的实现,用模板方法定义支付的标准流程。

// 支付模板:定义标准流程
public abstract class PaymentTemplate {
    
    // 模板方法,定义支付流程骨架
    public final PaymentResult pay(PaymentRequest request) {
        // 1. 参数校验
        validate(request);
        // 2. 调用第三方支付
        PaymentResult result = doPay(request);
        // 3. 处理回调
        handleCallback(result);
        // 4. 记录日志
        logPayment(request, result);
        return result;
    }
    
    protected abstract void validate(PaymentRequest request);
    protected abstract PaymentResult doPay(PaymentRequest request);
    protected abstract void handleCallback(PaymentResult result);
    
    private void logPayment(PaymentRequest request, PaymentResult result) {
        // 通用日志记录
    }
}

// 微信支付实现
public class WechatPayment extends PaymentTemplate {
    @Override
    protected void validate(PaymentRequest request) {
        // 微信特有的参数校验
    }
    
    @Override
    protected PaymentResult doPay(PaymentRequest request) {
        // 调用微信支付API
        return null;
    }
    
    @Override
    protected void handleCallback(PaymentResult result) {
        // 处理微信回调
    }
}

关键点:模板方法把不变的流程固定下来,策略模式把变化的实现抽离出去。这样新增一个支付渠道,只需要写一个实现类,不需要改动现有代码。这就是开闭原则的体现。

订单状态管理:状态模式

订单的状态流转是电商系统里最容易出bug的地方。一个订单可能有:待支付、已支付、已发货、已签收、已取消、退款中、已退款……每个状态能做什么操作,完全不一样。

状态模式来管理,每个状态都是一个独立的对象,状态之间的转换由状态对象自己控制。

// 订单状态接口
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 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 cancel(Order order) {
        System.out.println("已支付订单取消,进入退款流程");
        order.setState(new RefundingState());
    }
}

避坑指南:我曾经接手过一个项目,订单状态用int类型存储,代码里到处都是if(state == 1)这种判断。后来加了一个新状态,改了一堆代码,还漏改了两个地方,导致线上bug。用状态模式之后,新增状态只需要加一个类,改动的风险降到最低。

通知服务:观察者模式 + 适配器模式

订单状态变化后,需要通知用户。通知方式也很多样:短信、邮件、App推送、站内信……

这里用观察者模式最合适:订单状态变化时,通知所有订阅了该事件的观察者。每个观察者负责一种通知方式。如果第三方通知服务的接口不统一,再用适配器模式做一层封装。

// 事件发布器
public class OrderEventPublisher {
    private List<OrderObserver> observers = new ArrayList<>();
    
    public void addObserver(OrderObserver observer) {
        observers.add(observer);
    }
    
    public void publish(OrderEvent event) {
        for (OrderObserver observer : observers) {
            observer.onOrderEvent(event);
        }
    }
}

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

// 短信通知适配器
public class SmsNotificationAdapter implements OrderObserver {
    private SmsService smsService; // 第三方短信服务
    
    @Override
    public void onOrderEvent(OrderEvent event) {
        // 适配第三方短信接口
        smsService.send(event.getUserId(), buildMessage(event));
    }
}

嗯,到这里,电商系统的核心链路和对应的设计模式就梳理清楚了。你会发现,这些模式不是硬套上去的,而是业务本身的需求驱动出来的。说白了,设计模式是为了解决业务复杂度而生的,不是为了炫技。

下一节我们会继续深入,看看日志审计、缓存策略、分布式事务等场景中,设计模式又是怎么发挥作用的。


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