☰
Java Builder 模式实战指南:基于 java-design-patterns 仓库的 Hero 构建器深度解析
2026/10/1 2:43:12 网站建设 项目流程
  • 示例工程
  • 教程

【免费下载链接】java-design-patterns

Design patterns implemented in Java

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载

Builder(构建器)模式是 Gang of Four 提出的经典创建型模式之一,它允许我们分步骤构造复杂对象,并把"对象的构造过程"与"对象的最终表示"彻底解耦,从而用同一套构造流程产出不同形态的对象。本指南以 java-design-patterns 仓库中builder模块(德语文档位于 localization/de/builder/README.md,英文原版见 builder/README.md)为主线,结合 Hero.java、App.java 及对应测试源码,完整讲解 Builder 模式的原理、Joshua Bloch 风格实现、参数校验、枚举取值与适用场景,读完你即可在自己的 Java 项目中复刻这一套"流式(fluent)构建"方案。

一、Builder 模式的目的与核心思想

Builder 是一个创建型(creational)模式,其核心目的在于:

将复杂对象的构造过程与其**表示(representation)**分离,使得同一个构造过程可以创建出不同的表示。

通俗地说,Builder 允许你在**不编写大量重载构造器(constructor pollution)**的情况下,灵活地"拼装"出同一对象的多种形态;尤其适合那些创建过程步骤繁多、或对象存在多种"风味"组合的场景。

真实世界类比:定制三明治

想象你在熟食店定制一份三明治。你不需要知道三明治内部究竟如何组装,只需要通过一个SandwichBuilder,一步一步指定想要的组件——面包种类、肉类、奶酪、蔬菜、酱料。构造流程与最终成品分离,意味着基于同样的组件,可以按不同选择组合出截然不同的三明治。这正是 Builder 模式在现实中的投影:使用者只需按步骤声明需求,装配细节由构建器封装。

一句话概括

你可以创建同一对象的多种形态,而无需维护一堆构造函数重载或一个参数冗长的构造函数;当对象的创建步骤很多、或对象可能出现多种"口味"时,Builder 尤其有用。

二、Builder 要解决的痛点:Telescoping Constructor 反模式

Wikipedia 将 Builder 描述为"旨在解决 Telescoping Constructor(伸缩构造器)反模式的对象创建型设计模式"。这个反模式长什么样?几乎所有开发者都见过类似的构造函数:

public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon){ // 值赋值 }

问题显而易见:

  • 参数数量迅速膨胀,难以分辨每个参数的含义与顺序;
  • 一旦后续新增可选选项,参数列表会继续变长;
  • 为了支持不同参数组合,往往还要叠加多层重载,形成"伸缩式"的构造函数金字塔。

这就是Telescoping Constructor Antipattern。Builder 模式的使命就是用"一个逐步接收初始化参数的构建器对象"取代"大量构造函数组合",最终一次性返回构建完成的对象。

构建时序

下图展示了 Builder 模式的调用时序(图片来源 builder/etc/builder-sequence-diagram.png):

三、仓库中的程序化示例:用 Builder 构建 Hero

在 builder 模块 中,我们用 Builder 构建带不同属性的Hero对象。场景设定是角色扮演游戏的角色生成器:最简单的方式是让计算机全自动生成角色;但玩家有时希望手动挑选职业、发色、护甲等属性,这个选择过程逐步进行,直到所有想要的特征确定下来才算完成。用 Builder 正是更明智的解法。

3.1 产品类 Hero

仓库中的Hero以Java record形式定义(Hero.java),字段全部为final,不可变对象与 Builder 天然契合:

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() 及嵌套 Builder 类见下文 }

要点:

  • 私有构造器只接收Builder,外部无法绕过 Builder 直接实例化;
  • 构造时从builder中一次性取走全部六个属性;
  • toString()依据非空字段动态拼装描述文本(如with ... hair、wearing ...、wielding a ...),注意对BALD(秃头)做了特判,输出head而非hair(见 Hero.java)。

3.2 属性枚举:可选的取值空间

Builder 的可选参数全部来自枚举,这为"可选项"提供了明确的取值范围(源码位于 builder/src/main/java/com/iluwatar/builder):

枚举可取值
Profession(职业,必填)WARRIOR、THIEF、MAGE、PRIEST
HairType(发型,可选)BALD、SHORT、CURLY、LONG_STRAIGHT、LONG_CURLY
HairColor(发色,可选)WHITE、BLOND、RED、BROWN、BLACK
Armor(护甲,可选)CLOTHES、LEATHER、CHAIN_MAIL、PLATE_MAIL
Weapon(武器,可选)DAGGER、SWORD、AXE、WARHAMMER、BOW

其中HairType与Armor通过 Lombok 的@AllArgsConstructor注入展示用标题文本(如"chain mail"),并重写toString()返回小写可读形式;Profession、HairColor、Weapon则直接以枚举名小写作为展示文本。

3.3 嵌套 Builder:Joshua Bloch 风格

Hero.Builder是Hero的静态嵌套类(Hero.java),采用了《Effective Java》第二版中 Joshua Bloch 推荐的实现变体。模块入口 App.java 的 Javadoc 明确说明了这一点,并指出 Builder 的另一大优势:它非常适合构造"扁平数据"型对象(如 HTML 代码、SQL 查询、X.509 证书等)——这类数据无法逐步编辑、必须一次性给出,用 Builder 类构造是最佳途径。

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); } }

这套实现蕴含的规范要点:

  • 必填参数进构造器:profession与name必须在new Hero.Builder(...)时提供,且被声明为final,从机制上杜绝"忘填必填项";
  • 构造器内即校验:任一必填参数为null时立即抛出IllegalArgumentException("profession and name can not be null"),实现"快速失败"(fail-fast);
  • 可选参数用流式方法:每个withXxx方法设置字段后返回this,支持链式调用(fluent interface);
  • build()收尾:一次性调用私有构造器生成不可变的Hero实例。

3.4 使用示例与程序输出

App.java 演示了三种角色的构建:

public static void main(String[] args) { 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 则只选了发型与武器——这正是"同一个构造过程产出不同表示"的直观体现。

程序输出(经由@Slf4j注入的LOGGER打印):

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.

3.5 测试验证

仓库测试从两个维度保证了模式实现的行为正确性:

  • HeroTest.java:
    • testMissingProfession/testMissingName验证必填参数校验——传入null职业或null名字都会触发IllegalArgumentException(HeroTest.java);
    • testBuildHero构建一个全属性 Warrior(Sir Lancelot),逐项断言profession()、name()、armor()、weapon()、hairType()、hairColor()与构建请求一致(HeroTest.java),印证了"构建器忠实反映所有请求属性"的契约;
  • AppTest.java:验证App.main执行全程不抛异常。

3.6 类结构一览

builder模块的 UML 类图(PlantUML 源文件见 builder/etc/builder.urm.puml)展示了整体结构:Hero聚合六个属性字段并依赖嵌套Builder(Builder ..+ Hero),Builder与五个枚举分别关联。该模块pom.xml位于 builder/pom.xml,可通过项目根目录的./mvnw在builder模块下运行./mvnw -pl builder test或直接运行App复现上述输出。

四、适用场景:什么时候使用 Builder

综合文档与实现,当以下条件成立时 Builder 是最佳选择:

  1. 应用需要复杂对象的构造,构造函数参数繁多;
  2. 复杂对象的构造算法应独立于其组成部分以及这些部分的组装方式;
  3. 构造过程必须允许产出所构造对象的不同表示;
  4. 特别适用于创建步骤多、且步骤需要按特定顺序执行的场景;
  5. 对象为不可变设计、或包含"扁平数据"(一次性成型的 HTML/SQL/X.509 等)时,Builder 几乎是最优解。

五、Java 生态中的真实应用

Builder 在 JDK 与主流框架中随处可见(以下均为文档列出的知名实例,可结合 builder/README.md 原文查看):

  • StringBuilder与StringBuffer:逐步构造可变字符串对象;
  • java.nio.ByteBuffer及FloatBuffer、IntBuffer等缓冲区类;
  • javax.swing.GroupLayout.Group#addComponent();
  • IDE 中的各类 GUI 构建器,用于组装 UI 组件;
  • 所有java.lang.Appendable的实现类;
  • Apache Camel 的 builder 工具集;
  • Apache Commons CLI 的Option.Builder。

六、优点与代价

优点(Benefits):

  • 相比其他创建型模式,对构造过程有更强的控制力;
  • 支持逐步构造、延迟构造步骤、甚至递归执行步骤;
  • 可以构造需要复杂子对象组装的对象,最终产品与其组成部分及组装过程完全解耦;
  • 符合单一职责原则(SRP):复杂的构造代码可与产品的业务逻辑隔离。

代价(Trade-offs):

  • 由于需要新增 Builder 类,整体代码复杂度可能上升;
  • 构建过程中创建的多个 Builder 对象可能增加内存占用。

七、与其他模式的关系

  • Abstract Factory:可与 Builder 协同使用,由抽象工厂生成复杂对象的各个部分;
  • Prototype:Builder 常常基于原型来创建对象;
  • Step Builder:Builder 的一种变体,采用逐步(step-by-step)方式生成复杂对象;当对象拥有大量可选参数、且希望规避 Telescoping Constructor 反模式时,Step Builder 是很好的选择。

八、参考与延伸阅读

本文依据的德语文档与英文原版在 localization/de/builder/README.md、builder/README.md,涉及源码集中在 builder/src/main/java/com/iluwatar/builder。模式的理论源头可追溯至《Design Patterns: Elements of Reusable Object-Oriented Software》(GoF 著)、《Effective Java》(Joshua Bloch,本书正是仓库该实现变体的出处)、《Head First Design Patterns》与《Refactoring to Patterns》等经典著作。

小结:在 java-design-patterns 仓库的builder模块中,Builder 模式以"必填参数入构造器 + 可选参数流式链 + 构造时校验 + 不可变产品"的完整形态落地。对照 Hero.java 与 HeroTest.java 逐行研读,即可将这套模式迁移到任何"参数多、可选性强、追求不可变"的 Java 业务对象上。

  • 示例工程
  • 教程

【免费下载链接】java-design-patterns

Design patterns implemented in Java

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载
上一篇:Repomix 隐私策略深度解析:CLI、网站与浏览器扩展的数据处理边界
下一篇:Taichi RFC 解读:AOT 支持所有 SNode——SNode 树类型化与字段本地化的设计之路

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询