导论 解决的问题 建造者设计模式核心解决的是”复杂对象的分步构建 “问题。
当一个对象字段很多,并且有的是必选,有的是可选,一般来说我们会写多个构造函数进行重载,然后复用某个构造函数。
但是这样的实现往往可读性很差,必选字段和可选字段耦合在一起。
为了解决这一个问题,我们可以使用建造者模式来进行优化。
核心实现 在 Java 中,建造者模式的实现通常我们借助public
的静态内部类 (一般命名为Builder
)来实现
使用场景 只要遇到下面的几个场景,就可以考虑使用Builder进行重构
对象字段多,必选和可空字段耦合在多个重载的构造函数,经常会需要传null
进行实例化
对象的部分字段存在依赖关系,实例化流程存在依赖校验逻辑
代码实现 场景举例 我们以电商系统中的 “订单对象”为例:订单包含必选属性 (订单 ID、用户 ID、商品列表)和可选属性 (优惠券、配送地址、支付方式、备注),且部分属性有依赖关系(如选择 “货到付款” 则无法使用优惠券)
1 2 3 4 5 6 7 8 9 10 11 public class Order { private String orderId; private String userId; private List<String> goods; private String couponId; private String address; private String payType; private String remark; }
不使用设计模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Order { private String orderId; private String userId; private List<String> goods; private String couponId; private String address; private String payType; private String remark; public Order (String orderId, String userId, List<String> goods) { this .orderId = orderId; this .userId = userId; this .goods = goods; } public Order (String orderId, String userId, List<String> goods, String couponId) { this (orderId, userId, goods); this .couponId = couponId; } public Order (String orderId, String userId, List<String> goods, String couponId, String address) { this (orderId, userId, goods, couponId); this .address = address; } public Order (String orderId, String userId, List<String> goods, String couponId, String address, String payType) { this (orderId, userId, goods, couponId, address); this .payType = payType; } public void validate () { } }
我们的客户端一般来说是Service类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class OrderService { public static void main (String[] args) { String orderId = "ORDER_20240520_001" ; String userId = "USER_1001" ; List<String> goods = Arrays.asList("手机" , "耳机" ); Order order1 = new Order (orderId, userId, goods, "COUPON_50" , "北京市朝阳区" , "ALIPAY" ); Order order2 = new Order (orderId, userId, goods, null , "上海市浦东新区" , null ); Order order3 = new Order (orderId, userId, goods, "COUPON_30" , "广州市天河区" , "CASH_ON_DELIVERY" ); order3.setPayType("WECHAT" ); } }
使用建造者模式
新建静态内部类Builder,将必要参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static class Builder { private final String orderId; private final String userId; private final List<String> goods; private String couponId; private String address; private String payType; private String remark; public Builder (String orderId, String userId, List<String> goods) { this .orderId = orderId; this .userId = userId; this .goods = goods; } }
私有化构造函数,入参只有Builder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class Order { private String orderId; private String userId; private List<String> goods; private String couponId; private String address; private String payType; private String remark; private Order (Builder builder) { this .orderId = builder.orderId; this .userId = builder.userId; this .goods = builder.goods; this .couponId = builder.couponId; this .address = builder.address; this .payType = builder.payType; this .remark = builder.remark; } public static class Builder { private final String orderId; private final String userId; private final List<String> goods; private String couponId; private String address; private String payType; private String remark; public Builder (String orderId, String userId, List<String> goods) { this .orderId = orderId; this .userId = userId; this .goods = goods; } } }
实现Builder类链式set方法以及核心构建最终对象的build()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public static class Builder { private final String orderId; private final String userId; private final List<String> goods; private String couponId; private String address; private String payType; private String remark; public Builder (String orderId, String userId, List<String> goods) { this .orderId = orderId; this .userId = userId; this .goods = goods; } public Order build () { if (payType != null && !payType.isEmpty() && "CASH_ON_DELIVERY" .equals(payType) && couponId != null && !couponId.isEmpty()) { throw new IllegalArgumentException ("PAY ON DELIVERED NOT SUPPORT COUPON" ); } return new Order (this ); } public Builder setCouponId (String couponId) { this .couponId = couponId; return this ; } public Builder setAddress (String address) { this .address = address; return this ; } public Builder setPayType (String payType) { this .payType = payType; return this ; } public Builder setRemark (String remark) { this .remark = remark; return this ; } }
客户端只能先实例化 Builer 对象后调用build()
才能实例业务对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class OrderService { public static void main (String[] args) { String orderId = "ORDER_20240520_001" ; String userId = "USER_1001" ; List<String> goods = Arrays.asList("手机" , "耳机" ); Order order1 = new Order .Builder(orderId, userId, goods) .setCouponId("COUPON_50" ) .setAddress("北京市朝阳区" ) .setPayType("ALIPAY" ) .build(); Order order2 = new Order .Builder(orderId, userId, goods) .setAddress("上海市浦东新区" ) .build(); Order order3 = new Order .Builder(orderId, userId, goods) .setCouponId("COUPON_30" ) .setPayType("CASH_ON_DELIVERY" ) .build(); } }
总结 一般情况下,当出现下述的场景时,可以考虑使用建造者设计模式进行重构:
对象字段数量多且复杂,并且部分字段是可选的。
现有的构造函数很多而且臃肿,可选字段只能通过传递null
的方式才能实例化,可读性依托。
业务对象的字段之间存在某些关联,需要在实例化阶段进行校验。
针对上述的痛点,使用建造者设计模式来重构可以根据以下的步骤:
区分必选字段和可选字段都有哪些。
在对象类中新增静态内部类Builder
,将必选字段作为Builder
的构造函数参数。
私有化业务对象的构造函数,并只接收Builder
参数,防止外部直接实例化。
对于可选字段,Builder
中提供setter()和build()
,只有build()
才能实例化业务对象。
根据业务需要,在build()
中先进行逻辑校验后再返回业务对象实例。
通过上述的重构实现,我们不仅简化了复杂对象多个字段实例化时构造函数臃肿的问题,同时也更加优雅地处理了业务对象依赖字段的校验逻辑。