本篇文章,我将深入讲解建造者模式在电商订单系统中的实际应用,展示如何通过这一模式优雅地构建复杂订单对象。
一、业务场景分析
电商系统中,订单创建是一个复杂的过程,涉及多个必填和可选参数:
-
必填项:订单ID、用户ID、商品列表、总金额
-
可选项:优惠信息、发票信息、收货地址、备注
-
约束条件:
- 订单ID必须唯一
- 商品列表不能为空
- 总金额必须与商品金额总和匹配
- 如果使用优惠券,必须验证有效性
传统的构造器或setter方法会导致:
- 构造器参数过多难以维护
- setter方法无法保证对象创建过程的原子性
- 参数验证逻辑分散
二、建造者模式实现
1. 订单实体类设计
java
public class Order {
// 必填字段
private final String orderId;
private final Long userId;
private final List<OrderItem> items;
private final BigDecimal totalAmount;
// 可选字段
private final CouponInfo coupon;
private final InvoiceInfo invoice;
private final ShippingAddress address;
private final String remark;
// 私有构造器,只能通过Builder创建
private Order(Builder builder) {
this.orderId = builder.orderId;
this.userId = builder.userId;
this.items = Collections.unmodifiableList(builder.items);
this.totalAmount = builder.totalAmount;
this.coupon = builder.coupon;
this.invoice = builder.invoice;
this.address = builder.address;
this.remark = builder.remark;
}
// getter方法...
public static Builder builder(String orderId, Long userId) {
return new Builder(orderId, userId);
}
// 建造者静态内部类
public static class Builder {
// 必填参数
private final String orderId;
private final Long userId;
// 可选参数
private List<OrderItem> items = new ArrayList<>();
private BigDecimal totalAmount = BigDecimal.ZERO;
private CouponInfo coupon;
private InvoiceInfo invoice;
private ShippingAddress address;
private String remark;
public Builder(String orderId, Long userId) {
this.orderId = Objects.requireNonNull(orderId);
this.userId = Objects.requireNonNull(userId);
}
public Builder items(List<OrderItem> items) {
this.items = new ArrayList<>(items); // 防御性拷贝
return this;
}
public Builder addItem(OrderItem item) {
this.items.add(Objects.requireNonNull(item));
return this;
}
public Builder totalAmount(BigDecimal amount) {
this.totalAmount = Objects.requireNonNull(amount);
return this;
}
public Builder coupon(CouponInfo coupon) {
this.coupon = coupon;
return this;
}
public Builder invoice(InvoiceInfo invoice) {
this.invoice = invoice;
return this;
}
public Builder address(ShippingAddress address) {
this.address = address;
return this;
}
public Builder remark(String remark) {
this.remark = remark;
return this;
}
public Order build() {
validate();
calculateTotal(); // 自动计算总金额
return new Order(this);
}
private void validate() {
if (orderId.isEmpty()) {
throw new IllegalArgumentException("订单ID不能为空");
}
if (items.isEmpty()) {
throw new IllegalArgumentException("订单必须包含至少一件商品");
}
if (coupon != null && !coupon.isValid()) {
throw new IllegalArgumentException("优惠券已失效");
}
}
private void calculateTotal() {
if (totalAmount.compareTo(BigDecimal.ZERO) == 0) {
// 未设置总金额时自动计算
totalAmount = items.stream()
.map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 应用优惠
if (coupon != null) {
totalAmount = coupon.applyDiscount(totalAmount);
}
}
}
}
}
2. 订单项实体类
java
public class OrderItem {
private final String sku;
private final String name;
private final BigDecimal price;
private final int quantity;
public OrderItem(String sku, String name, BigDecimal price, int quantity) {
this.sku = Objects.requireNonNull(sku);
this.name = Objects.requireNonNull(name);
this.price = Objects.requireNonNull(price);
if (quantity <= 0) {
throw new IllegalArgumentException("数量必须大于0");
}
this.quantity = quantity;
}
// getter方法...
}
3. 订单服务中使用
java
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
@Autowired
private CouponService couponService;
@Transactional
public String createOrder(OrderRequest request) {
// 验证库存
request.getItems().forEach(item ->
inventoryService.checkStock(item.getSku(), item.getQuantity())
);
// 构建订单
Order.Builder builder = Order.builder(
generateOrderId(),
request.getUserId()
);
// 添加商品项
request.getItems().forEach(item ->
builder.addItem(new OrderItem(
item.getSku(),
item.getName(),
item.getPrice(),
item.getQuantity()
))
);
// 应用优惠券
if (request.getCouponCode() != null) {
CouponInfo coupon = couponService.validateCoupon(
request.getUserId(),
request.getCouponCode()
);
builder.coupon(coupon);
}
// 设置其他信息
builder.address(request.getAddress())
.remark(request.getRemark());
// 构建订单
Order order = builder.build();
// 保存订单
return orderRepository.save(order).getId();
}
}
三、建造者模式的优势
参数设置灵活:可选参数可以自由组合,不必记忆参数顺序
java
// 多种构建方式
Order order1 = Order.builder("123", 456L)
.addItem(item1)
.addItem(item2)
.build();
Order order2 = Order.builder("789", 101L)
.items(itemList)
.coupon(coupon)
.address(address)
.build();
对象不可变:构建完成后对象状态不可修改,线程安全
java
// 编译错误,无法修改
order.setTotalAmount(newAmount);
构建过程可控:可以在build()方法中添加验证逻辑
java
public Order build() {
validate();
calculateTotal();
return new Order(this);
}
代码可读性强:链式调用使代码更清晰
scss
Order.builder(id, userId)
.addItem(item1)
.addItem(item2)
.coupon(coupon)
.build();
参数隔离:建造者实例是独立的,可以复用
java
Order.Builder builder = Order.builder("123", 456L)
.address(address);
Order order1 = builder.addItem(item1).build();
Order order2 = builder.addItem(item2).build();
四、与其他创建型模式对比
模式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
建造者模式 | 创建复杂对象,特别是可选参数多的情况 | 参数灵活,对象不可变 | 需要额外创建Builder类 |
工厂方法 | 创建单一类型对象 | 解耦具体类 | 不适合复杂对象创建 |
抽象工厂 | 创建产品族 | 支持产品族扩展 | 难以支持新种类产品 |
五、实际业务扩展
1. 订单折扣策略
java
public interface DiscountStrategy {
BigDecimal applyDiscount(BigDecimal amount);
}
public class CouponDiscount implements DiscountStrategy {
private final BigDecimal discountAmount;
public CouponDiscount(BigDecimal discountAmount) {
this.discountAmount = discountAmount;
}
@Override
public BigDecimal applyDiscount(BigDecimal amount) {
return amount.subtract(discountAmount).max(BigDecimal.ZERO);
}
}
// 在Builder中使用
public Builder discountStrategy(DiscountStrategy strategy) {
this.discountStrategy = strategy;
return this;
}
private void calculateTotal() {
totalAmount = items.stream()
.map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (discountStrategy != null) {
totalAmount = discountStrategy.applyDiscount(totalAmount);
}
}
2. 订单历史版本
java
public class OrderHistory {
private final Order order;
private final Instant createdAt;
public OrderHistory(Order order) {
this.order = order;
this.createdAt = Instant.now();
}
// 恢复订单到该版本
public Order restore() {
return Order.builder(order.getOrderId(), order.getUserId())
.items(new ArrayList<>(order.getItems()))
.totalAmount(order.getTotalAmount())
// 设置其他字段...
.build();
}
}
六、最佳实践建议
- 将Builder作为静态内部类:保持与目标类的紧密关联
- 必填参数通过构造器传入:保证必要的初始化
- 返回this实现链式调用:提升代码可读性
- build()方法执行验证:确保对象有效性
- 考虑对象不可变性:构建完成后字段设为final
- 防御性拷贝:保护内部数据不被修改
java
public Builder items(List<OrderItem> items) {
this.items = new ArrayList<>(items); // 防御性拷贝
return this;
}
七、完整订单服务示例
java
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepo;
@Autowired
private InventoryService inventoryService;
@Autowired
private CouponService couponService;
@Autowired
private NotificationService notificationService;
@Override
public String placeOrder(OrderRequest request) {
// 1. 验证库存
validateInventory(request.getItems());
// 2. 验证优惠券
CouponInfo coupon = validateCoupon(request);
// 3. 构建订单
Order order = buildOrder(request, coupon);
// 4. 保存订单
orderRepo.save(order);
// 5. 扣减库存
inventoryService.deductStock(request.getItems());
// 6. 发送通知
notificationService.sendOrderCreatedNotification(order);
return order.getOrderId();
}
private void validateInventory(List<OrderItemRequest> items) {
items.forEach(item ->
inventoryService.checkStock(item.getSku(), item.getQuantity())
);
}
private CouponInfo validateCoupon(OrderRequest request) {
if (request.getCouponCode() == null) {
return null;
}
return couponService.validateCoupon(
request.getUserId(),
request.getCouponCode()
);
}
private Order buildOrder(OrderRequest request, CouponInfo coupon) {
Order.Builder builder = Order.builder(
generateOrderId(),
request.getUserId()
);
// 添加商品项
request.getItems().forEach(item ->
builder.addItem(convertToOrderItem(item))
);
// 设置可选信息
if (coupon != null) {
builder.coupon(coupon);
}
if (request.getAddress() != null) {
builder.address(request.getAddress());
}
if (request.getRemark() != null) {
builder.remark(request.getRemark());
}
return builder.build();
}
private OrderItem convertToOrderItem(OrderItemRequest request) {
return new OrderItem(
request.getSku(),
request.getName(),
request.getPrice(),
request.getQuantity()
);
}
private String generateOrderId() {
return "ORD-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
}
}
通过建造者模式,我们实现了:
- 复杂订单对象的优雅构建
- 参数验证的集中管理
- 不可变对象的线程安全
- 代码可读性和可维护性的提升