大家好,我是冰点,今天我们继续拆解设计模式,这次要聊的是建造者模式。
前阵子帮同事调试一个“订单创建”的bug时,我发现他写的Order类构造方法里堆了12个参数——有必填的“商品ID”“用户ID”,也有可选的“优惠券ID”“备注信息”“收货地址”,每次创建订单都要记参数顺序,漏填一个就报错。更麻烦的是,有时候需要创建“普通订单”,有时候需要创建“预售订单”(多了“付定金时间”“尾款时间”),用一个构造方法根本兼容不了。后来我建议他用建造者模式重构,把订单的“构建步骤”和“最终对象”分开,代码瞬间清爽了很多。
为什么需要建造者模式?先解决“复杂对象创建”的痛点
很多人会觉得“创建对象不就是new一下吗”,但遇到属性多、构建步骤复杂、存在多种变体的对象时,直接new会踩很多坑。建造者模式的核心价值,就是解决这类复杂对象的创建问题,主要针对三个痛点:
避免“参数爆炸”的构造方法。像前面的订单类,12个参数的构造方法不仅难记,还容易传错顺序(比如把“金额”和“数量”位置搞反),用建造者模式可以分步骤设置属性,不用一次性传所有参数。
分离“构建过程”和“对象表示”。比如创建“普通订单”需要3步(设商品、设用户、算金额),创建“预售订单”需要5步(多了设定金、设尾款时间),这些步骤可以交给建造者管理,业务代码不用关心“怎么一步步建订单”,只需要拿到最终的订单对象。
保证对象创建的完整性和一致性。用建造者模式可以强制执行必要的构建步骤(比如必须先设商品ID才能算金额),避免创建出“缺胳膊少腿”的无效对象(比如没有商品ID的订单)。
简单说,当你需要创建的对象“属性多、步骤多、有多种构建方式”时,建造者模式就是最优解之一。
建造者模式怎么实现?核心是“指挥者+建造者+产品”
它的实现思路很清晰:把对象的构建拆成多个步骤,由“建造者”负责执行步骤,“指挥者”负责控制步骤顺序,最终生成完整的“产品”对象。还是用“订单创建”的场景举例,带你看完整实现。
第一步:定义“产品”类(要创建的复杂对象)
先定义Order类,包含各种属性(必填+可选),注意把构造方法私有化,避免外部直接new,只能通过建造者创建。
// 产品:订单类
public class Order {
// 必填属性
private String productId; // 商品ID
private String userId; // 用户ID
private double amount; // 订单金额
// 可选属性
private String couponId; // 优惠券ID
private String remark; // 备注
// 预售订单专属属性
private long depositTime; // 付定金时间
private long balanceTime; // 付尾款时间
// 私有化构造方法,只能通过建造者创建
private Order(Builder builder) {
this.productId = builder.productId;
this.userId = builder.userId;
this.amount = builder.amount;
this.couponId = builder.couponId;
this.remark = builder.remark;
this.depositTime = builder.depositTime;
this.balanceTime = builder.balanceTime;
}
// getter方法(省略setter,保证对象创建后不可变,避免后续修改)
public String getProductId() { return productId; }
// 其他getter省略...
// 第二步:定义“建造者”(静态内部类,负责分步构建)
public static class Builder {
// 必选属性(直接在Builder中初始化,强制设置)
private String productId;
private String userId;
private double amount;
// 可选属性(默认值初始化)
private String couponId = "";
private String remark = "";
private long depositTime = 0;
private long balanceTime = 0;
// Builder的构造方法,强制传入必选属性
public Builder(String productId, String userId, double amount) {
this.productId = productId;
this.userId = userId;
this.amount = amount;
}
// 可选属性的设置方法,返回Builder本身(链式调用)
public Builder setCouponId(String couponId) {
this.couponId = couponId;
return this; // 关键:返回this,支持链式调用
}
public Builder setRemark(String remark) {
this.remark = remark;
return this;
}
// 预售订单专属属性的设置方法
public Builder setPresaleInfo(long depositTime, long balanceTime) {
this.depositTime = depositTime;
this.balanceTime = balanceTime;
return this;
}
// 最终构建产品的方法
public Order build() {
// 这里可以加校验逻辑,保证对象完整性(比如商品ID不能为空)
if (productId == null || productId.isEmpty()) {
throw new IllegalArgumentException("商品ID不能为空");
}
return new Order(this); // 调用Order的私有构造方法
}
}
}
第三步:在业务中使用建造者创建对象
业务代码里不用关心订单的构建步骤,直接通过Builder的链式调用设置属性,最后调用build()拿到完整订单,代码非常简洁。
public class OrderService {
public static void main(String[] args) {
// 1. 创建普通订单(只设必填+部分可选属性)
Order normalOrder = new Order.Builder("prod_001", "user_123", 99.9)
.setCouponId("coupon_456")
.setRemark("请尽快发货")
.build(); // 最终构建订单
// 2. 创建预售订单(多设预售专属属性)
Order presaleOrder = new Order.Builder("prod_002", "user_456", 199.9)
.setCouponId("coupon_789")
.setPresaleInfo(1699999999000L, 1700000000000L) // 定金时间+尾款时间
.build();
// 使用订单对象(调用getter方法)
System.out.println("普通订单商品ID:" + normalOrder.getProductId());
System.out.println("预售订单尾款时间:" + presaleOrder.getBalanceTime());
}
}
(可选)复杂场景下加“指挥者”
如果构建步骤特别复杂(比如创建订单需要先算折扣、再减优惠券、最后加运费),可以加一个“指挥者”类,专门控制步骤顺序,让建造者只负责“执行步骤”,进一步解耦。
// 指挥者:控制订单构建的步骤顺序
public class OrderDirector {
private Order.Builder builder;
public OrderDirector(Order.Builder builder) {
this.builder = builder;
}
// 指挥构建“带折扣的普通订单”(固定步骤:设商品→算折扣→设优惠券)
public Order buildDiscountedOrder(double discount) {
double discountedAmount = 99.9 * discount; // 算折扣后的金额
return builder
.setCouponId("coupon_discount") // 自动加折扣优惠券
.build();
}
}
// 业务中使用指挥者
Order.Builder builder = new Order.Builder("prod_003", "user_789", 99.9);
OrderDirector director = new OrderDirector(builder);
Order discountedOrder = director.buildDiscountedOrder(0.8); // 直接拿到8折订单
实际用的时候,我总结的3个关键经验
建造者模式不算复杂,但用的时候容易和其他模式混淆,分享几个我踩过的坑和心得:
区分“建造者模式”和“工厂模式”。工厂模式是“一步创建对象”,适合创建简单或中等复杂度的对象;建造者模式是“分步创建对象”,适合创建属性多、步骤多的复杂对象。比如“创建简单的支付对象”用工厂,“创建复杂的订单对象”用建造者。
优先用“静态内部类Builder”。像上面的实现一样,把Builder作为产品类的静态内部类,好处是代码紧凑,且能访问产品类的私有构造方法,避免外部篡改。这是Java中最常用的建造者实现方式(比如StringBuilder、OkHttp的Request.Builder都是这种写法)。
对象创建后尽量“不可变”。产品类最好只提供getter方法,不提供setter方法,让对象创建后就固定下来,避免后续被意外修改(比如创建订单后,金额被随便改了),保证线程安全和数据一致性。
最后总结:什么时候优先用建造者模式?
记住三个判断标准,满足任一即可考虑:
对象有5个以上属性,且包含必填和可选属性;
对象创建需要多个步骤(比如算金额、校验参数、设置专属属性);
需要创建多种变体的对象(比如普通订单、预售订单、团购订单),但构建步骤有重叠。
接下来我们还会继续拆解其他设计模式,如果你对建造者模式有疑问,比如“怎么用建造者创建集合类型的对象”,或者想看看其他场景(比如创建复杂的文档对象)的实现,都可以评论区留言。
评论区