- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
建造者模式(Builder Pattern)是 GoF 23 种经典创建型模式之一,核心思想是把复杂对象的"构造过程"与"最终表示"分离,让同一套构造流程能够产出不同的对象形态。本文以开源仓库 java-design-patterns 的builder模块为蓝本,从"伸缩构造器反模式"问题出发,逐行剖析Hero与其内部Builder的源码实现、流式(fluent)调用链、参数约束与测试验证,读完即可在自己的 Java 项目中落地一套可复制的建造者方案。
目的:将复杂对象的构造与其表示分开
建造者模式的目的非常明确:将复杂对象的构造与其表示分开,以便同一构造过程可以创建不同的表示。在builder模块中,这一目的被完整落地为Hero(产品)与Hero.Builder(建造者)两个角色,二者共同位于 Hero.java。
在动手写代码之前,先理解它要解决的问题。
它要解决的核心问题:伸缩构造器反模式(Telescoping Constructor Antipattern)
维基百科对建造者模式的定义是:"建造者模式是一种对象创建的软件设计模式,旨在为伸缩构造器反模式寻找一个解决方案。"
所谓"伸缩构造器反模式",指的就是下面这类构造函数——参数越来越多、排列顺序越来越难理解:
public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { }从 App.java 的类注释可以看出,该模式引入的背景正是:对象构造参数的组合增加,会导致构造函数数量呈指数级膨胀("an exponential list of constructors")。而建造者模式的做法是:不再堆叠构造函数,而是引入一个独立的 builder 对象,让它逐步接收每一个初始化参数,最后一次性返回构造完成的对象。
此外,注释中还指出了建造者模式的另一大用途:对于包含"扁平数据"的对象——例如 HTML 代码、SQL 查询、X.509 证书等无法逐段编辑、只能一次性组装的数据,builder 是构建这类对象的最佳方式。
现实世界例子
想象一个角色扮演游戏的角色生成器。最简单的选择是让计算机为你创建角色。但是如果你想选择一些像专业、性别、发色等角色细节时,这个角色生成就变成了一个渐进的过程。当所有选择完成时,该过程也将完成。
用通俗的话说:
建造者模式允许你创建不同口味的对象,同时避免构造器污染。当一个对象可能有几种口味,或者一个对象的创建涉及到很多步骤时会很有用。
下面这张类图来自仓库的builder/etc目录,展示了Hero、Builder与各属性类型之间的关系:
编程示例:从 Hero 到 Hero.Builder 的完整实现
第一步:产品类 Hero
在仓库当前实现中,Hero是一个record(Java 16+),由六个字段构成:
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); } // ... }关键点:
Hero的公开构造路径只有一条——经由Builder的私有构造函数,外部无法绕过 builder 直接 new;- 六个字段中,
profession与name是构建时的必填项,其余四项均为可选; Hero本身不可变(record 的字段天然 final),构建完成后任何属性都无法再修改,这保证了对象的线程安全与可安全发布。
第二步:属性枚举
profession、发色、发型、护甲、武器五类属性分别由五个枚举承载:
| 枚举 | 可选值 | 文件 |
|---|---|---|
Profession | WARRIOR、THIEF、MAGE、PRIEST | Profession.java |
HairColor | WHITE、BLOND、RED、BROWN、BLACK | HairColor.java |
HairType | BALD、SHORT、CURLY、LONG_STRAIGHT、LONG_CURLY | HairType.java |
Armor | CLOTHES、LEATHER、CHAIN_MAIL、PLATE_MAIL | Armor.java |
Weapon | DAGGER、SWORD、AXE、WARHAMMER、BOW | Weapon.java |
其中HairType与Armor通过 Lombok 的@AllArgsConstructor注入展示用文案(如LONG_CURLY→ "long curly"),并重写toString()返回小写/友好文本,以便最终输出可读的角色描述。
第三步:建造者 Builder
Builder是Hero的静态内部类,遵循Joshua Bloch 在《Effective Java》中描述的 Builder 变体(这一点在 App.java 注释中有明确说明)。核心机制有三个:
- 构造函数只收必填参数,并做防御性校验:
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; }- 可选参数通过流式方法(fluent interface)逐步设置,每个方法返回
this支持链式调用:
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; }build()作为终点,一次性产出不可变产品:
public Hero build() { return new Hero(this); }第四步:实际使用
在 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());注意三个角色的差异:mage只设置发色与武器,warrior使用了全部四个可选参数,thief甚至没有设置发色和护甲——这正是"同一构造过程可以创建不同表示"的直接体现,同时完全绕开了参数顺序易错的构造器地狱。
实际运行输出(由Hero.toString()生成,逻辑见 Hero.java 第 46-69 行):
This is a mage named Riobard with black hair and wielding a dagger. This is a warrior named Amberjill with blond long curly hair wearing chain mail and wielding a sword. This is a thief named Desmond with bald head and wielding a bow.toString()的实现本身也演示了如何优雅处理可选字段为null的情况(如未设置发型时只输出发色,BALD时输出 "head" 而非 "hair")。
如何运行本示例
模块采用 Maven 管理,仓库根目录提供了 Maven Wrapper,可以在仓库根目录直接运行:
./mvnw -pl builder test # 运行 builder 模块的全部测试 ./mvnw -pl builder exec:java # 或直接运行主程序查看输出运行主程序后,控制台会打印出上面三行角色描述日志。
源码佐证:测试用例如何验证 Builder 行为
builder模块的测试位于 builder/src/test/java/com/iluwatar/builder,覆盖了建造者的两个关键约束:
- HeroTest.java 的
testMissingProfession与testMissingName:分别以null职业、null姓名构造Hero.Builder,断言抛出IllegalArgumentException,验证必填参数的防御性校验确实生效; - 同文件
testBuildHero:通过完整链式调用构建一个WARRIOR英雄,再逐字段断言profession()、name()、armor()、weapon()、hairType()、hairColor()与设置值一致,验证 builder 的参数传递正确性; - AppTest.java:直接执行
App.main,断言整个示例程序可无异常跑通。
适用性:什么时候应该使用建造者模式
原文档明确指出,在以下场景使用建造者模式:
- 创建复杂对象的算法应独立于组成对象的零件及其组装方式;
- 构造过程必须允许所构造的对象具有不同的表示形式;
- 当一个产品需要很多步骤才能创建完成,且这些步骤需要按特定顺序执行时,该模式尤其适用。
换句话说,"产品本身复杂" + "存在多种可选组合"是使用 Builder 的两大信号。
优点与权衡
优点:
- 相比其他创建型模式,对构造过程拥有更细粒度的控制;
- 支持分步构造对象,可以延后某些构造步骤,甚至递归地执行步骤;
- 能够构造需要复杂组装子对象的对象,最终产品与其组成部分及组装过程解耦;
- 遵循单一职责原则:把复杂的构造代码从产品的业务逻辑中隔离出来。
权衡:
- 由于需要额外创建 builder 类,整体代码复杂度会有所上升;
- 需要创建多个 builder 对象,可能带来一定的内存开销。
Java 世界中的真实应用
建造者模式在 Java 生态中随处可见,最常见的实例包括:
java.lang.StringBuilder/java.lang.StringBuffer:链式append逐步拼装字符串;java.nio.ByteBuffer及同类缓冲(FloatBuffer、IntBuffer等);java.lang.Appendable的全部实现类;javax.swing.GroupLayout.Group#addComponent()式的 GUI 组件逐步组装;- 各类 IDE 内置的 UI 构建器;
- Apache Camel 的 builder 包、Apache Commons CLI 的
Option.Builder等框架级实现。
与相关设计模式的关系
- 抽象工厂(Abstract Factory):可以与建造者配合使用,由抽象工厂生产复杂对象的各个组成部分,再由 Builder 完成最终组装;
- 原型(Prototype):Builder 常基于一个原型对象来创建新对象;
- 步骤建造者(Step Builder):是 Builder 的一种变体,采用分步式 API 构造复杂对象。当对象拥有大量可选参数、又希望彻底规避伸缩构造器反模式时,Step Builder 是更优选择(仓库中同样有独立的 step-builder 模块可供对照学习)。
参考与致谢
本模块的实现思路与文档内容参考了以下经典著作:
- 《Design Patterns: Elements of Reusable Object-Oriented Software》(GoF 原书)
- 《Effective Java》(Joshua Bloch,Builder 变体的出处)
- 《Head First Design Patterns: A Brain-Friendly Guide》
- 《Refactoring to Patterns》
小结
回顾整个builder模块:必填参数进构造器、可选参数走流式方法、build()一次性产出不可变对象——这套骨架完整诠释了"将构造过程与表示分离"的模式意图。结合 Hero.java 的 record 实现、App.java 的多角色演示以及 HeroTest.java 的参数校验测试,你可以在自己的项目中直接套用同一套模式,用可读、可扩展、参数安全的 API 取代臃肿的构造函数。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
Java Builder 模式实战:以 java-design-patterns 的 Hero 构建为例彻底告别构造器污染
Java Builder 模式实战:以 java design patterns 的 Hero 构建为例彻底告别构造器污染 Builder(建造者)模式是 Go
示例工程教程Java 设计模式之 Builder(建造者)模式:java-design-patterns 仓库中的 Hero 构建实战
Java 设计模式之 Builder(建造者)模式:java design patterns 仓库中的 Hero 构建实战 Builder(建造者)模式是 Ja
示例工程教程使用 Java Builder 模式构建复杂对象:以 java-design-patterns 仓库的 Hero 示例为例
使用 Java Builder 模式构建复杂对象:以 java design patterns 仓库的 Hero 示例为例 导读 Builder(建造者)是 G
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考