建造者模式
你用过 HttpClient 吗?看看它的 Builder 配置有多少项:
如果这些都用构造函数参数,代码会变成什么样?HttpClient(String version, long timeout, int redirectMode, ...) —— 这样的 API 没人愿意用。
这就是建造者模式的价值:用链式调用解决多参数构造的问题。
为什么需要建造者模式?
重叠构造函数的灾难
假设有一个 User 类,有 5 个可选参数:
调用方必须为每个 null 参数提供占位符,阅读代码时根本不知道哪些参数是真正重要的。
JavaBean 模式的问题
问题在于:
- 对象状态不一致:构造函数创建了一个不完整的对象,在
setXxx()调用完成前,对象都是无效的 - 无法表示不可变性:setter 意味着对象可以随时修改
- 线程安全问题:在多线程环境下,一个线程可能在另一个线程还未完成配置时使用该对象
建造者模式结构
传统结构包含 Director、Builder 接口、具体 Builder 和 Product。但在实际 Java 开发中,我们更常用「流式 API」的实现方式。
链式调用的实现
不可变对象构建
建造者模式天然适合构建不可变对象。关键点:
- 成员变量用
final修饰 - 不提供 setter 方法
- 构造函数私有化
- Builder 负责初始化所有字段
Lombok @Builder 原理
Lombok 的 @Builder 注解会自动生成 Builder 类:
编译后会生成:
Lombok @Builder 的坑
1. 默认值处理
解决方案:用 @Builder.Default
2. 继承问题
@Builder 不支持继承体系中的父类字段。
解决方案:使用 @SuperBuilder(Lombok 1.18.10+)
建造者模式 vs 工厂模式
思考题
问题 1:为什么说建造者模式适合构建不可变对象?
参考答案
不可变对象的三个核心特征:
- 所有字段都是 final 的
- 不提供 setter
- 字段如果是引用类型,返回时做防御性拷贝
建造者模式天然符合这些要求:
同时,建造过程是原子性的——只有调用 build() 后才生成完整对象,使用者无法看到「半成品」状态。
相比之下,JavaBean 模式在 setXxx() 调用过程中对象都是无效的,不符合不可变性的要求。
问题 2:Lombok 的 @Builder 生成的对象是线程安全的吗?
参考答案
Builder 类本身不是线程安全的。
生成的对象是否线程安全取决于类本身。如果类的字段都是 final 且没有 setter,对象创建后自然是线程安全的(只读)。
如果需要线程安全的构建过程,应该:
问题 3:什么情况下不应该用建造者模式?
参考答案
1. 参数少且简单
2. 对象不可变需求不强烈
如果对象需要经常修改,使用 Builder 会增加代码复杂度。
3. 产品结构简单
没有复杂的组装过程,一行 new 就能解决。
4. 需要高性能的场景
Builder 模式会创建额外的 Builder 对象,虽然开销很小,但在极端性能敏感的场景下可能需要注意。