Builder模式解决构造函数参数过多导致可读性差、易出错的问题,通过分离必填与可选参数、避免重载爆炸、支持校验,使对象创建过程清晰可控。

Builder模式到底解决什么问题
它不是为了炫技,而是当你写 new Person("张三", 25, "北京", true, false, null, "138...", "高级工程师", "...") 这种10+参数的构造函数时,编译器没报错,但你自己已经看不懂哪个参数对应哪个字段了——Builder就是来救这个场的。
核心就三点:
• 避免“伸缩构造函数”(Telescoping Constructor)爆炸
• 把可选参数和必填参数在语义上分开
• 让对象创建过程变成“可读、可跳过、可校验”的步骤
- 常见错误现象:构造函数重载太多,IDE自动补全时选错重载,运行时报
NullPointerException却找不到是哪个字段漏设 - 适用场景:对象含 ≥4 个参数,其中 ≥2 个为可选;或需要构建不可变对象(
final字段) - 性能影响极小:只是多一次对象分配(
Builder实例),远小于反射或JSON反序列化开销
静态内部类Builder怎么写才不踩坑
这是目前Java中最常用、最轻量的Builder写法,但新手常犯两个硬伤:字段未用final导致构建中途被篡改,或build()里忘了校验必填项。
正确姿势:
立即学习“Java免费学习笔记(深入)”;
public class Order {
private final String orderId;
private final String userId;
private final BigDecimal amount;
private final String remark; // 可选
private Order(Builder builder) {
this.orderId = builder.orderId;
this.userId = builder.userId;
this.amount = builder.amount;
this.remark = builder.remark;
}
public static class Builder {
private final String orderId; // 必填,构造时传入
private final String userId;
private final BigDecimal amount;
private String remark; // 可选,setter提供
public Builder(String orderId, String userId, BigDecimal amount) {
this.orderId = orderId;
this.userId = userId;
this.amount = amount;
}
public Builder remark(String remark) {
this.remark = remark;
return this;
}
public Order build() {
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalStateException("amount must be positive");
}
return new Order(this);
}
}
}
- 必填字段必须在
Builder构造时传入,且声明为final,防止后续被覆盖 -
build()方法是唯一校验入口,所有业务规则(如非空、范围、格式)都放这里 - 不要在
remark(String)里做校验——它只是设置,校验只发生在build()
Director + ConcreteBuilder经典四角色还用不用
用,但只在一种情况:你需要同一套构建流程,产出多个不同结构的对象,比如GamingComputerBuilder和OfficeComputerBuilder都走“装CPU→装内存→装硬盘”流程,但每步实现不同。
绝大多数业务代码根本不需要这么重——你不会同时维护5个PersonBuilder子类。这时候硬套四角色,只会让调用方多写三行无意义代码:
ComputerBuilder builder = new GamingComputerBuilder(); Director director = new Director(); Computer pc = director.construct(builder); // 多此一举
- 现代项目中,90%的Builder需求用静态内部类就够了
- 只有当你明确需要“算法复用+表现分离”(比如配置中心动态加载不同Builder实现),才考虑抽象
Builder接口和Director - Lombok的
@Builder注解生成的就是静态内部类版,别为“看起来像设计模式”而放弃它
Builder和构造函数、工厂方法到底怎么选
别纠结“该用哪个模式”,看参数特征和演化预期:
- 参数 ≤3 个,全必填 → 直接用构造函数,干净利落
- 参数 ≥4 个,含可选 → 优先用静态内部类
Builder,哪怕只写3行 - 对象创建逻辑涉及外部资源(如DB查默认值、HTTP调用)、或需缓存/代理 → 才轮到工厂方法(
OrderFactory.create()) - 如果未来可能支持JSON/YAML配置驱动构建(如Spring Boot的
@ConfigurationProperties),Builder的字段命名和类型要尽量对齐POJO,别加业务逻辑
最容易被忽略的一点:Builder不是万能胶。当你的“复杂对象”其实是多个独立实体组合(比如Order包含Address、ItemList、Payment),别在一个Builder里硬塞所有setXXX——应该让每个子对象自己提供Builder,再由外层组装。否则,校验边界模糊,单元测试难写,重构时牵一发而动全身。










