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