☰
Java 设计模式之 Builder(构建者)模式:用 java-design-patterns 仓库源码吃透对象构建
2026/10/2 1:58:14 网站建设 项目流程
  • 示例工程
  • 教程

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

Design patterns implemented in Java

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

本篇技术指南以 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) { // 参数赋值 }

问题显而易见:

  1. 参数数量迅速失控:当可选属性越来越多时,构造函数的形参列表不断膨胀,调用方难以记忆每个参数的位置与含义;
  2. 可读性差:new Hero(Profession.MAGE, "Riobard", HairType.BALD, HairColor.BLACK, null, Weapon.DAGGER)这类代码里,null的位置含义模糊,极易传错;
  3. 扩展性差:后续每增加一个可选属性,要么继续加参数,要么新增重载构造函数,最终形成"望远镜式"(层层叠加)的构造函数爆炸。

这正是 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()映射)为构建者提供了清晰的取值集合:

枚举文件路径取值
ProfessionProfession.javaWARRIOR、THIEF、MAGE、PRIEST
HairTypeHairType.javaBALD、SHORT、CURLY、LONG_STRAIGHT、LONG_CURLY
HairColorHairColor.javaWHITE、BLOND、RED、BROWN、BLACK
ArmorArmor.javaCLOTHES、LEATHER、CHAIN_MAIL、PLATE_MAIL
WeaponWeapon.javaDAGGER、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

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

相关推荐

上一篇:MiniMind快速本地部署:轻量GPT跑起来 + WebUI搭建
下一篇:深度解析Video2X:构建高性能视频超分辨率与帧插值应用的全栈实战指南

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

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

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

立即咨询