- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
本篇技术指南以 localization/ko/builder/README.md(韩文版 Builder 模式文档)为核心骨架,结合本仓库
builder模块的真实源码、枚举定义与单元测试进行深度展开。你将理解 Builder 模式如何解决"伸缩构造函数反模式"(telescoping constructor anti-pattern),掌握其类结构、链式调用写法、参数校验策略与适用场景,并看到它在 JDK 与流行框架中的真实应用。
设计意图(Intent)
Builder 是一种创建型设计模式,其核心意图是:将复杂对象的构建过程与其最终表示分离,使得同一个构建过程可以产出不同的对象表示。
用一句话概括:我们不再直接通过构造函数一次性传齐所有参数,而是借助一个独立的"构建者"对象,分步地、可读地、可校验地组装目标对象,最后再一次性产出最终产物。这在 java-design-patterns 仓库中对应 builder 模块,其分类标签为Creational(创建型)与Gang of Four(GoF 经典四人组模式)。
要解决的问题:伸缩构造函数反模式(Telescoping Constructor Anti-pattern)
为什么需要 Builder?文档中给出了一个几乎所有 Java 开发者都见过的痛点——构造参数过多的构造函数:
public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { // 参数赋值 }问题显而易见:
- 参数数量迅速失控:当可选属性越来越多时,构造函数的形参列表不断膨胀,调用方难以记忆每个参数的位置与含义;
- 可读性差:
new Hero(Profession.MAGE, "Riobard", HairType.BALD, HairColor.BLACK, null, Weapon.DAGGER)这类代码里,null的位置含义模糊,极易传错; - 扩展性差:后续每增加一个可选属性,要么继续加参数,要么新增重载构造函数,最终形成"望远镜式"(层层叠加)的构造函数爆炸。
这正是 Wikipedia 对 Builder 模式的定义背景——Builder 模式是旨在解决伸缩构造函数反模式的对象创建型软件设计模式。
仓库源码实现:Hero 与 Builder 的完整结构
本仓库的 builder/src/main/java/com/iluwatar/builder 包中,给出了一个基于 RPG 角色生成场景的完整示例。角色创建是一个典型的分步过程:职业、姓名是必填项,而发型、发色、护甲、武器都是可选项,选择全部完成后角色才最终"生成"。
目标对象 Hero
Hero.java中,Hero被定义为一个record(Java 16+ 特性),所有字段均为final,天然不可变:
public record Hero( Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { private Hero(Builder builder) { this( builder.profession, builder.name, builder.hairType, builder.hairColor, builder.armor, builder.weapon); } // toString() 省略 }要点分析:
- 构造函数是
private的:外部无法直接new Hero(...),只能经由Builder间接创建,从语言层面强制了构建流程的唯一入口; - 对象不可变:record 的访问器方法(
profession()、name()等)只读,构建完成后对象状态不会改变,天然线程安全、便于缓存与共享; - 构造即拷贝:私有构造函数从 builder 的各字段拷贝到 record 的规范构造函数,完成"快照式"赋值。
构建者 Builder
Hero.Builder是嵌套在Hero内部的public static类(文档中称之为"the builder"),它的设计体现了 Builder 模式的经典三要素:
| 角色 | 实现方式 |
|---|---|
| 必填参数 | 放入Builder构造函数,并做非空校验 |
| 可选参数 | 通过withXxx(...)链式方法逐步设置,方法返回this |
| 终止方法 | build()一次性调用私有构造函数产出最终对象 |
public static class Builder { private final Profession profession; // 必填,final private final String name; // 必填,final private HairType hairType; // 可选 private HairColor hairColor; // 可选 private Armor armor; // 可选 private Weapon weapon; // 可选 public Builder(Profession profession, String name) { if (profession == null || name == null) { throw new IllegalArgumentException("profession and name can not be null"); } this.profession = profession; this.name = name; } public Builder withHairType(HairType hairType) { this.hairType = hairType; return this; } public Builder withHairColor(HairColor hairColor) { this.hairColor = hairColor; return this; } public Builder withArmor(Armor armor) { this.armor = armor; return this; } public Builder withWeapon(Weapon weapon) { this.weapon = weapon; return this; } public Hero build() { return new Hero(this); } }几个值得细读的源码细节:
- 必填参数的强约束:
Builder构造函数内显式抛出IllegalArgumentException,杜绝了"职业或姓名为空"的角色对象产生。这是文档中"점층적 생성자 안티-패턴에 대한 해결책"(对伸缩构造函数反模式的解决方案)在代码层面的落地; - 流式接口(Fluent Interface):每个
withXxx方法返回this,使调用可以连续拼接;可选参数不设置即为null,最终toString()中会对空值做条件判断(见 Hero.java,如hairType != HairType.BALD ? "hair" : "head"的趣味处理); - 单步产出:
build()是唯一的出口,调用时一次性完成对象组装,符合App.java中"configuration is ready then call build method"的流程描述。
支撑枚举:可选项的取值域
构建过程中用到的各属性均为枚举,它们的定义(含toString()映射)为构建者提供了清晰的取值集合:
| 枚举 | 文件路径 | 取值 |
|---|---|---|
Profession | Profession.java | WARRIOR、THIEF、MAGE、PRIEST |
HairType | HairType.java | BALD、SHORT、CURLY、LONG_STRAIGHT、LONG_CURLY |
HairColor | HairColor.java | WHITE、BLOND、RED、BROWN、BLACK |
Armor | Armor.java | CLOTHES、LEATHER、CHAIN_MAIL、PLATE_MAIL |
Weapon | Weapon.java | DAGGER、SWORD、AXE、WARHAMMER、BOW |
其中HairType与Armor使用 Lombok 的@AllArgsConstructor绑定展示文案(如LONG_CURLY显示为 "long curly"),Profession等则重写toString()返回小写枚举名——这也解释了程序输出中This is a mage named Riobard这类自然语言句子的来源。
使用方式与运行输出
App.java的main方法演示了三种不同"风味"的角色构建,充分体现"同一构建过程、不同对象表示":
var mage = new Hero.Builder(Profession.MAGE, "Riobard") .withHairColor(HairColor.BLACK) .withWeapon(Weapon.DAGGER) .build(); LOGGER.info(mage.toString()); var warrior = new Hero.Builder(Profession.WARRIOR, "Amberjill") .withHairColor(HairColor.BLOND) .withHairType(HairType.LONG_CURLY) .withArmor(Armor.CHAIN_MAIL) .withWeapon(Weapon.SWORD) .build(); LOGGER.info(warrior.toString()); var thief = new Hero.Builder(Profession.THIEF, "Desmond") .withHairType(HairType.BALD) .withWeapon(Weapon.BOW) .build(); LOGGER.info(thief.toString());程序输出(与文档记录一致):
16:28:06.058 [main] INFO com.iluwatar.builder.App -- This is a mage named Riobard with black hair and wielding a dagger. 16:28:06.060 [main] INFO com.iluwatar.builder.App -- This is a warrior named Amberjill with blond long curly hair wearing chain mail and wielding a sword. 16:28:06.060 [main] INFO com.iluwatar.builder.App -- This is a thief named Desmond with bald head and wielding a bow.注意三个角色的构建差异:mage 只设置了发色与武器,warrior 补全了全部可选属性,thief 则只设了发型与武器——可选属性完全按需组合,这正是 Builder 相比"全参构造函数"的核心优势。运行方式也很简单:在 IDE 中直接执行App.main,或在仓库根目录通过 Maven 运行对应测试。
模式类图与调用时序
文档中的类图(./etc/builder.urm.png)展示了Hero、Builder与各枚举之间的静态关系;builder/etc/builder.urm.puml 是它的 PlantUML 源文件,仓库还额外提供了时序图 builder/etc/builder-sequence-diagram.png 说明运行时调用链。
从时序上看,调用流程为:new Hero.Builder(必填参数)→ 零到多次.withXxx(可选参数)→.build()返回不可变的Hero实例。整个过程中调用方完全不需要知道Hero内部有多少字段、如何组装。
适用场景(Applicability)
根据文档与源码,以下情况适合使用 Builder 模式:
- 复杂对象的构造算法应与组成部分及组装方式解耦——即"怎么拼"与"拼什么"互不依赖;
- 构建过程需要允许同一对象产生不同表示——例如同一个
Hero.Builder流程可产出法师、战士、盗贼三种形态; - 产品需要大量步骤才能创建,且步骤需要按特定顺序执行(顺序约束可由 builder 内部方法设计保证);
- 可选参数多、必填参数少的场景,用于规避伸缩构造函数反模式。
此外,App.java 的类注释还补充了一个容易被忽略的适用场景:对象包含"扁平数据"(如 HTML 代码、SQL 查询、X.509 证书)时,这类数据无法逐步编辑、必须一次性整体生成,builder 是构造它们的最佳方式。
单元测试验证
仓库提供了完整的测试用例来印证 Builder 的契约行为:
- HeroTest.java:
testMissingProfession:new Hero.Builder(null, "Sir without a job")断言抛出IllegalArgumentException;testMissingName:new Hero.Builder(Profession.THIEF, null)同样断言抛异常——验证必填参数校验逻辑;testBuildHero:完整构建一个骑士角色后,逐一断言profession()、name()、armor()、weapon()、hairType()、hairColor()与设置值一致——验证 builder 正确将配置传递给了最终对象;
- AppTest.java:断言
App.main可无异常执行,确保示例程序可运行。
优点与权衡(Benefits and Trade-offs)
优点:
- 相比其他创建型模式,对构建过程有更强的控制力——可以分步构建、延迟执行某些步骤,甚至递归执行步骤;
- 支持需要复杂子对象组装的场景,最终产品与其组成部分及组装过程完全解耦;
- 符合单一职责原则:把复杂的构造代码从产品业务逻辑中隔离出来;
- 参数校验集中、可读性高、产物不可变。
权衡:
- 由于需要额外创建 builder 类,整体代码复杂度会上升;
- 多个 builder 对象的创建可能带来额外的内存占用。
与仓库中其他模式的关联
在 java-design-patterns 仓库中,Builder 并非孤立存在,它与若干模式相互配合:
- 抽象工厂(Abstract Factory):可与 Builder 联合使用,由抽象工厂负责构建复杂对象的零部件,Builder 负责装配流程;
- 原型(Prototype):Builder 常基于原型对象进行定制化复制构建;
- 逐步构建者(Step Builder):仓库 step-builder 模块是其变体——当可选参数非常多、且需要编译期强制按步骤构建时,Step Builder 通过接口类型逐步引导调用顺序,同样是伸缩构造函数反模式的替代方案。
真实世界应用案例
Builder 模式在 Java 生态中随处可见,文档列举了以下经典实例:
java.lang.StringBuilder——最广为人知的字符串构建器;java.lang.StringBuffer——线程安全的可变字符串构建器;java.nio.ByteBuffer及其同类缓冲区(FloatBuffer、IntBuffer等)——通过链式put分步填充数据;- 所有实现了
java.lang.Appendable的类型; - Apache Camel 的 builder 工具集(
org.apache.camel.builder包),用于分步构建路由与 DSL; - Apache Commons CLI 的
Option.Builder,用于链式组装命令行选项。
这些实例的共同特征:构建对象包含大量可选配置,且希望最终对象不可变或一次性生成,与本文Hero.Builder的动机完全一致。
参考资料与延伸阅读
本模块内容与以下经典著作一脉相承,可作深入学习参考:
- Design Patterns: Elements of Reusable Object-Oriented Software(GoF 四人组经典)
- Effective Java——Joshua Bloch 提出的 Builder 变体正是本仓库
Hero.Builder的蓝本(见 App.java 类注释中的明确说明) - Head First Design Patterns
- Refactoring to Patterns
如需查看英文原版说明与完整示例,可对照仓库根级文档 builder/README.md,其与韩文版 localization/ko/builder/README.md 共同维护同一份builder模块的技术说明,二者互为印证。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
Java 设计模式之 Builder(建造者)模式:java-design-patterns 仓库中的 Hero 构建实战
Java 设计模式之 Builder(建造者)模式:java design patterns 仓库中的 Hero 构建实战 Builder(建造者)模式是 Ja
示例工程教程Java 设计模式之 Builder 构建器模式:在 java-design-patterns 中用清晰的方式构造复杂对象
Java 设计模式之 Builder 构建器模式:在 java design patterns 中用清晰的方式构造复杂对象 Builder(构建器)是一种创建型
示例工程教程Java 设计模式之 Context Object(上下文对象)模式:java-design-patterns 仓库源码级解读与实战指南
Java 设计模式之 Context Object(上下文对象)模式:java design patterns 仓库源码级解读与实战指南 导读 本文以 java
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考