设计模式|建造者设计模式Remastered

导论

解决的问题

建造者设计模式核心解决的是”复杂对象的分步构建“问题。

当一个对象字段很多,并且有的是必选,有的是可选,一般来说我们会写多个构造函数进行重载,然后复用某个构造函数。

但是这样的实现往往可读性很差,必选字段和可选字段耦合在一起。

为了解决这一个问题,我们可以使用建造者模式来进行优化。

核心实现

在 Java 中,建造者模式的实现通常我们借助public静态内部类(一般命名为Builder)来实现

使用场景

只要遇到下面的几个场景,就可以考虑使用Builder进行重构

  • 对象字段多,必选和可空字段耦合在多个重载的构造函数,经常会需要传null进行实例化
  • 对象的部分字段存在依赖关系,实例化流程存在依赖校验逻辑

代码实现

场景举例

我们以电商系统中的 “订单对象”为例:订单包含必选属性(订单 ID、用户 ID、商品列表)和可选属性(优惠券、配送地址、支付方式、备注),且部分属性有依赖关系(如选择 “货到付款” 则无法使用优惠券)

1
2
3
4
5
6
7
8
9
10
11
public class Order {
// 必选属性
private String orderId; // 订单ID
private String userId; // 用户ID
private List<String> goods; // 商品列表
// 可选属性
private String couponId; // 优惠券ID(可选)
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; // 订单ID
private String userId; // 用户ID
private List<String> goods; // 商品列表
// 可选属性
private String couponId; // 优惠券ID(可选)
private String address; // 配送地址(可选)
private String payType; // 支付方式(可选)
private String remark; // 订单备注(可选)

// 痛点1:构造方法膨胀——必选+可选属性组合导致重载泛滥
public Order(String orderId, String userId, List<String> goods) {
this.orderId = orderId;
this.userId = userId;
this.goods = goods;
}

// 重载1:添加优惠券
public Order(String orderId, String userId, List<String> goods, String couponId) {
this(orderId, userId, goods);
this.couponId = couponId;
}

// 重载2:添加优惠券+地址
public Order(String orderId, String userId, List<String> goods, String couponId, String address) {
this(orderId, userId, goods, couponId);
this.address = address;
}

// 重载3:添加优惠券+地址+支付方式...(属性越多,重载越多)
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() {
//如果选择货到付款则无法再次使用优惠券
}

//省略 getter setter
}

我们的客户端一般来说是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("手机", "耳机");

// 痛点2:参数顺序易混淆——若需添加备注,需找到对应重载,且参数位置易写错
Order order1 = new Order(orderId, userId, goods, "COUPON_50", "北京市朝阳区", "ALIPAY");

// 痛点3:可选属性缺失时,需传null占位,代码丑陋且易出错
Order order2 = new Order(orderId, userId, goods, null, "上海市浦东新区", null);

// 痛点4:构建过程无校验——若选择“货到付款”却传了支付方式,无法在创建时拦截
Order order3 = new Order(orderId, userId, goods, "COUPON_30", "广州市天河区", "CASH_ON_DELIVERY");
order3.setPayType("WECHAT"); // 后续修改对象可能破坏逻辑
}
}

使用建造者模式

  1. 新建静态内部类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;
}

}
  1. 私有化构造函数,入参只有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; // 订单ID
private String userId; // 用户ID
private List<String> goods; // 商品列表
// 可选属性
private String couponId; // 优惠券ID(可选)
private String address; // 配送地址(可选)
private String payType; // 支付方式(可选)
private String remark; // 订单备注(可选)

// 禁止外部直接实例化 Order 对象
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;
}

}
}
  1. 实现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;
}

}
  1. 客户端只能先实例化 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的方式才能实例化,可读性依托。
  • 业务对象的字段之间存在某些关联,需要在实例化阶段进行校验。

针对上述的痛点,使用建造者设计模式来重构可以根据以下的步骤:

  1. 区分必选字段和可选字段都有哪些。
  2. 在对象类中新增静态内部类Builder,将必选字段作为Builder的构造函数参数。
  3. 私有化业务对象的构造函数,并只接收Builder参数,防止外部直接实例化。
  4. 对于可选字段,Builder中提供setter()和build(),只有build()才能实例化业务对象。
  5. 根据业务需要,在build()中先进行逻辑校验后再返回业务对象实例。

通过上述的重构实现,我们不仅简化了复杂对象多个字段实例化时构造函数臃肿的问题,同时也更加优雅地处理了业务对象依赖字段的校验逻辑。


设计模式|建造者设计模式Remastered
http://example.com/2025/09/16/设计模式-建造者设计模式Remastered/
作者
Noctis64
发布于
2025年9月16日
许可协议