从盖房子到写代码:用建造者模式重构你的‘烂’代码(真实案例复盘)

张开发
2026/4/19 21:06:32 15 分钟阅读

分享文章

从盖房子到写代码:用建造者模式重构你的‘烂’代码(真实案例复盘)
从订单构建到代码优雅用建造者模式重构复杂业务对象在电商系统开发中我们经常遇到需要构建复杂业务对象的场景。比如一个电商订单可能包含数十个字段用户信息、商品列表、优惠券、支付方式、配送地址、发票信息等等。传统的做法可能是创建一个包含所有参数的庞大构造函数或者编写一堆setter方法逐个设置属性。这样的代码不仅难以阅读和维护还容易因为遗漏某些必填字段而导致运行时错误。这就是典型的代码坏味道——构造函数参数过多。1. 问题诊断为什么需要建造者模式让我们从一个真实的电商订单案例开始。假设我们有一个Order类最初的设计可能是这样的public class Order { private String orderId; private Long userId; private ListOrderItem items; private BigDecimal totalAmount; private BigDecimal discountAmount; private BigDecimal paymentAmount; private String paymentMethod; private String shippingAddress; private String invoiceTitle; private String invoiceContent; private String couponCode; private Date createTime; private Date payTime; // 还有更多字段... // 超长的构造函数 public Order(String orderId, Long userId, ListOrderItem items, BigDecimal totalAmount, BigDecimal discountAmount, BigDecimal paymentAmount, String paymentMethod, String shippingAddress, String invoiceTitle, String invoiceContent, String couponCode, Date createTime, Date payTime /* 更多参数... */) { // 各种参数校验和赋值 } // 或者一堆setter方法 public void setOrderId(String orderId) { /*...*/ } public void setUserId(Long userId) { /*...*/ } // 更多setter... }这种设计存在几个明显问题构造困难调用者需要记住所有参数的顺序和类型容易出错可读性差调用代码难以理解每个参数的含义不变性难以保证使用setter方式时对象可能在构造过程中处于不一致状态参数组合爆炸有些参数是可选的有些是必填的导致需要多个重载构造函数提示当你的类有超过4个构造参数时就应该考虑使用建造者模式了。2. 建造者模式的核心结构建造者模式通过将复杂对象的构造过程分解为多个步骤并引入一个指挥者来管理构造流程从而解决上述问题。其核心包含四个角色Product产品最终要构建的复杂对象如OrderBuilder建造者接口定义构建产品的各个步骤ConcreteBuilder具体建造者实现Builder接口提供各步骤的具体实现Director指挥者负责按特定顺序调用建造者的方法来构造产品让我们用表格对比传统方式和建造者模式的区别对比维度传统方式建造者模式构造复杂度高需要一次性提供所有参数低分步骤设置可读性差参数含义不明确好方法名自描述不变性保证难以保证容易保证参数校验集中在校验逻辑复杂可分步骤校验构造流程控制固定灵活可定制不同构造流程代码维护性差修改参数影响所有调用方好修改只影响建造者内部3. 实战重构电商订单建造者现在让我们用建造者模式重构之前的Order类。首先定义产品类public class Order { private final String orderId; // 必填 private final Long userId; // 必填 private final ListOrderItem items; // 必填 private final BigDecimal totalAmount; private BigDecimal discountAmount; private String paymentMethod; private String shippingAddress; // 其他字段... // 私有构造函数只能通过建造者创建 private Order(Builder builder) { this.orderId builder.orderId; this.userId builder.userId; this.items builder.items; this.totalAmount calculateTotalAmount(items); this.discountAmount builder.discountAmount; this.paymentMethod builder.paymentMethod; this.shippingAddress builder.shippingAddress; // 其他字段赋值... } // 静态内部Builder类 public static class Builder { private String orderId; private Long userId; private ListOrderItem items; private BigDecimal discountAmount BigDecimal.ZERO; private String paymentMethod; private String shippingAddress; // 其他字段... public Builder(String orderId, Long userId, ListOrderItem items) { if (orderId null || userId null || items null || items.isEmpty()) { throw new IllegalArgumentException(必填参数不能为空); } this.orderId orderId; this.userId userId; this.items new ArrayList(items); // 防御性拷贝 } public Builder discountAmount(BigDecimal discountAmount) { this.discountAmount discountAmount; return this; } public Builder paymentMethod(String paymentMethod) { this.paymentMethod paymentMethod; return this; } public Builder shippingAddress(String shippingAddress) { this.shippingAddress shippingAddress; return this; } // 其他可选字段的设置方法... public Order build() { // 可以在这里进行最终校验 if (discountAmount.compareTo(BigDecimal.ZERO) 0) { throw new IllegalStateException(折扣金额不能为负数); } return new Order(this); } } // 其他方法... }使用这个建造者的客户端代码会变得非常清晰Order order new Order.Builder(ORD123456, 1001L, items) .discountAmount(new BigDecimal(50.00)) .paymentMethod(ALIPAY) .shippingAddress(北京市海淀区...) .build();这种方式的优势显而易见必填参数明确通过Builder构造函数强制要求可选参数灵活通过链式方法按需设置参数校验分散可以在设置方法和build()中进行不同粒度的校验对象状态一致一旦build()成功对象就是完整且一致的4. 高级应用变体与最佳实践建造者模式在实际应用中有多种变体和最佳实践下面介绍几种常见的高级用法4.1 多步骤复杂构建对于特别复杂的对象可以引入Director角色来封装常见的构建流程public class OrderDirector { private final Order.Builder builder; public OrderDirector(Order.Builder builder) { this.builder builder; } public Order constructStandardOrder() { return builder .paymentMethod(ALIPAY) .shippingAddress(getDefaultAddress()) .build(); } public Order constructPremiumOrder() { return builder .paymentMethod(WECHAT_PAY) .shippingAddress(getPremiumAddress()) .applyPremiumDiscount() .build(); } // 其他构建流程... } // 使用示例 Order.Builder builder new Order.Builder(ORD123, 1001L, items); OrderDirector director new OrderDirector(builder); Order standardOrder director.constructStandardOrder();4.2 不可变对象的构建建造者模式特别适合创建不可变对象因为可以在build()方法中一次性完成所有校验public class ImmutableOrder { private final String orderId; // 其他final字段... private ImmutableOrder(Builder builder) { // 在构造方法中完成所有校验 validate(builder); this.orderId builder.orderId; // 其他赋值... } public static class Builder { // ...同前 public ImmutableOrder build() { return new ImmutableOrder(this); } } private static void validate(Builder builder) { // 复杂的校验逻辑 if (/* 不满足条件 */) { throw new IllegalStateException(校验失败); } } }4.3 组合建造者当构建的对象包含其他复杂对象时可以使用组合建造者public class Order { private final ListOrderItem items; private final PaymentInfo paymentInfo; // 其他字段... public static class Builder { private ListOrderItem items; private PaymentInfo.Builder paymentInfoBuilder; public Builder withPaymentInfo(PaymentInfo.Builder paymentInfoBuilder) { this.paymentInfoBuilder paymentInfoBuilder; return this; } public Order build() { PaymentInfo paymentInfo paymentInfoBuilder ! null ? paymentInfoBuilder.build() : PaymentInfo.defaultInfo(); return new Order(this, paymentInfo); } } } // 使用示例 Order order new Order.Builder() .withItems(items) .withPaymentInfo(new PaymentInfo.Builder() .method(ALIPAY) .amount(totalAmount)) .build();4.4 与工厂模式的选择建造者模式与工厂模式都用于创建对象但适用场景不同场景建造者模式工厂模式对象复杂度复杂多部分构成相对简单构建过程分步骤构建一步创建参数灵活性高可选参数多低参数相对固定构建流程控制可以精细控制每个步骤隐藏具体创建细节典型应用配置对象、文档生成、订单构建依赖注入、插件系统5. 重构效果评估让我们从几个关键指标评估重构前后的代码质量5.1 可读性对比重构前Order order new Order( ORD123456, 1001L, items, new BigDecimal(500.00), new BigDecimal(50.00), ALIPAY, 北京市海淀区..., // 更多参数... );重构后Order order new Order.Builder(ORD123456, 1001L, items) .totalAmount(new BigDecimal(500.00)) .discountAmount(new BigDecimal(50.00)) .paymentMethod(ALIPAY) .shippingAddress(北京市海淀区...) .build();重构后的代码具有自描述性每个参数的含义一目了然。5.2 可维护性对比参数变更新增或删除参数时建造者模式只需修改Builder类不影响现有调用代码参数校验校验逻辑可以分散到各个setter方法和build()方法中更易维护构造流程复杂的构造逻辑可以封装在Director中避免重复代码5.3 扩展性对比可选参数建造者模式天然支持可选参数无需创建多个重载构造函数变体对象通过不同的Director可以轻松创建不同变体的对象组合构建可以方便地组合多个建造者来构建复杂对象图5.4 性能考量建造者模式会引入一些额外开销需要创建Builder对象可能有额外的参数拷贝方法调用次数增加但在大多数应用中这些开销可以忽略不计。只有当构建操作在性能关键路径上且被频繁调用时才需要考虑这些开销。6. 实际项目中的经验分享在大型电商系统中使用建造者模式处理订单构建时我们积累了一些宝贵经验必填参数处理将真正必填的参数放在Builder构造函数中确保不会遗漏。我们曾经因为把某些理论上可选但实际上业务必须的参数设为可选导致了一些生产问题。防御性拷贝对于集合类型参数如订单项列表在Builder中进行防御性拷贝避免外部修改影响已构建的对象。参数校验时机基础校验非空、格式等放在setter方法中业务规则校验如金额不能为负放在build()方法中跨字段校验如折扣不能大于总额放在产品类构造函数中线程安全性Builder通常不是线程安全的如果要在多线程环境中使用需要额外处理。我们一般每个线程使用独立的Builder实例。与JSON转换当需要与JSON相互转换时可以为Builder添加fromJson()方法或者使用Jackson的JsonPOJOBuilder注解与Lombok结合可以使用Lombok的Builder注解快速生成建造者但对于复杂场景手动实现的Builder更灵活。文档化为Builder的每个方法添加清晰的文档注释说明参数要求和业务含义这对团队协作非常重要。渐进式重构对于已有的大型类可以逐步引入建造者模式首先创建一个包含主要参数的Builder逐步将更多参数迁移到Builder中最后将原构造函数设为私有在最近的一个项目中我们使用建造者模式重构了一个包含37个参数的报表配置类使得客户端代码的可读性大幅提升配置错误导致的bug减少了约80%。

更多文章