资讯动态

别再传一排 null:Java Builder 模式 30 秒上手,java-design-patterns 的 Hero 示例

发布时间:2026/10/17 6:08:32 来源:尧图企业网站定制
别再传一排 nullJava Builder 模式 30 秒上手java-design-patterns 的 Hero 示例【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址: https://gitcode.com/GitHub_Trending/ja/java-design-patterns在 java-design-patterns 仓库里用 Builder 模式创建 RPG 角色 Hero 只需要三行代码构造函数传入职业与姓名with 方法链上发色、武器、盔甲build() 收尾产出对象。这篇 Builder 模式教程从「构造函数越来越长」的痛点讲起拆解它如何规避伸缩构造函数。创建的麻烦往往从一个构造函数开始。Hero 有 6 个属性职业和姓名必填其余全是可选。用构造函数写调用方就得每个坑位都填不关心的位置一律 nullnew Hero(Profession.MAGE, Riobard, null, HairColor.BLACK, null, Weapon.DAGGER)。中间的 null 是发型还是盔甲看代码的人得数一数位置才知道。更糟的是后面每加一个可选属性就得改构造函数签名或者再加一个重载这就是伸缩构造函数反模式的典型形态。Builder 的解法是给对象配一条装配线调用方只写自己关心的部分不关心的直接跳过。Builder 到底解决了什么和伸缩构造函数、工厂三方对比维度伸缩构造函数一次性工厂Builder可读性按位置传参null 无语义只能数坑位工厂参数清晰但返回什么取决于工厂内部with 方法名自带语义读起来就是「黑发、拿匕首」扩展性加一个可选属性 改签名或加一组重载加一个属性 工厂方法再加参数加一个 with 方法即可老调用不用动参数爆炸重载数量随可选属性指数增长全参方法退化成伸缩构造必填与可选分离组合方式稳定GoF 把 Builder 归在创建型模式。它和工厂的分界一句话能说清工厂一次性返回完整对象Builder 逐步装配、最后成型同一套流程允许产出不同表示。先动手三行代码生成一个 Herobuilder/src/main/java/com/iluwatar/builder/App.java 里就是三个角色的创建过程全部共用一条骨架var mage new Hero.Builder(Profession.MAGE, Riobard) .withHairColor(HairColor.BLACK) .withWeapon(Weapon.DAGGER) .build(); var warrior new Hero.Builder(Profession.WARRIOR, Amberjill) .withHairColor(HairColor.BLOND) .withHairType(HairType.LONG_CURLY) .withArmor(Armor.CHAIN_MAIL) .withWeapon(Weapon.SWORD) .build(); var thief new Hero.Builder(Profession.THIEF, Desmond) .withHairType(HairType.BALD) .withWeapon(Weapon.BOW) .build();运行输出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.三段调用长得几乎一样必填项进构造器可选项挑着链build() 收尾。形态的差异完全来自可选参数的组合而不是来自不同的代码路径——法师没写盔甲盗贼干脆没写发色对象就少那块。这正是「同一装配流程、不同参数组合、不同表示」的落地形态。源码是怎么实现的从 Hero 倒着读到枚举顺着数据流倒过来看更清楚build() 产出什么、谁在装配、调用方传入的取值域从哪来。5.1 产出Hero 本体Hero.java 里Hero 是一个 record外加一个私有构造器public record Hero( Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { private Hero(Builder builder) { // 私有构造器只有 Builder 能造出 Hero this(builder.profession, builder.name, builder.hairType, builder.hairColor, builder.armor, builder.weapon); } }record 自带 final 组件和 profession() 这类访问器所以 build() 产出的 Hero 是不可变对象天然线程安全可以放心到处传。构造器是私有的外部 new 不出来对象只能经由装配线产生。toString() 则按「哪些字段非空」拼出自然语言描述比如法师那句This is a mage named Riobard with black hair and wielding a dagger.光头会说 head 而不是 hair。5.2 装配Builder 静态内部类Builder 是 Hero 里的静态内部类装配状态全在它身上public static class Builder { private final Profession profession; // 必填装配入口固化 private final String name; private HairType hairType; // 可选默认 null // …hairColor、armor、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; // 返回自身链式调用 } // …withHairColor、withArmor、withWeapon 结构相同省略 public Hero build() { return new Hero(this); // 把装配状态一次性注入目标对象 } }五个关键机制对照如下机制代码落点作用必填参数进构造器Builder 构造方法收 profession、name字段 final缺了这两项Builder 都建不起来入口做 null 校验构造方法第一行抛 IllegalArgumentException错误在装配时暴露不会带进使用阶段可选项走 withXxx每个可选属性对应一个 with 方法调用方只写关心的项流式返回 thiswith 方法一律 return this形成流式接口可连写多步build() 收尾return new Hero(this)一次性产出不可变 Hero5.3 请求五个枚举with 方法能传的取值全部来自同包下的五个枚举它们界定了角色属性可选值的全集枚举取值ProfessionWARRIOR、THIEF、MAGE、PRIESTHairTypeBALD、SHORT、CURLY、LONG_STRAIGHT、LONG_CURLYHairColorWHITE、BLOND、RED、BROWN、BLACKArmorCLOTHES、LEATHER、CHAIN_MAIL、PLATE_MAILWeaponDAGGER、SWORD、AXE、WARHAMMER、BOW显示文本的约定只有一种HairType 与 Armor 用 Lombok 的 AllArgsConstructor 注入一个展示标题输出 long curly、chain mail其余三个枚举直接用小写枚举名Hero.toString() 的拼接就靠它。两张 UML 看懂静态与动态类图给出静态结构Hero 与 Hero.Builder 同居一个文件Builder 持有全部六个属性build() 关联到目标对象五个枚举是两者共享的取值域。时序图记录动态过程调用方 new 出 Builder 后逐个调用 with 方法每一步都拿回同一个对象最后 build() 返回不可变 Hero。类图的 PlantUML 源文件在 builder/etc/builder.urm.puml改完可以直接重新渲染成自己的版本。运行与验证如何运行 builder 模块的测试builder 是仓库多模块 Maven 工程下的独立子模块根目录有 Maven Wrapper两条命令即可./mvnw -pl builder test # 跑单元测试 ./mvnw -pl builder package # 打包模块单元测试 HeroTest 只测两件事用例断言内容testMissingProfession / testMissingName必填参数传 null 时Builder 构造器当场抛 IllegalArgumentExceptiontestBuildHero完整装配 Sir Lancelot六个字段逐一与请求一致前两条锁死「入口校验」契约后一条锁死「装配保真」契约——校验在装配入口生效可选参数不会在 build() 时丢失或串位。什么时候用、什么时候别用Builder 的适用边界值得上的场景有三个对象可选属性较多3 个以上而必填只有一两项同一条构造流程需要产出多种形态像示例里的法师、战士、盗贼希望目标对象不可变或者想把构造逻辑从产品类里挪出去。两个代价也要心里有数代价说明类数量翻倍每个产品类都要配一个 Builder 类多一个中间对象装配过程先建 Builder 再建产品超高频短生命周期场景要算这笔账分界线一句话属性固定、一次成型用简单工厂就行装配步骤有强制顺序要求时看仓库里的 step-builder 模块它用步骤对象把顺序也锁死。再进一步生态里的 Builder 应用与相关模式JDK 里的 Builder 应用很常见StringBuilder、StringBuffer 按步拼接字符串ByteBuffer 及 IntBuffer、FloatBuffer 等同类缓冲先经静态工厂方法取得再用 put() 系列逐步填充javax.swing GroupLayout.Group 的 addComponent() 是 GUI 组件装配Apache Commons CLI 的 Option.Builder 也是同一形态。横向看模式关系三种搭配各管一段搭配对象分工抽象工厂abstract-factory工厂产出部件Builder 按序组装适合多套产品线原型Prototype先复制现成对象再用 Builder 微调差异step-builder用步骤对象把装配顺序也锁死经典出处方面GoF《设计模式》定义了它的意图静态内部类的写法范本则出自 Joshua Bloch 的《Effective Java》。留三条记忆点必填项进构造器null 当场拒绝并用 final 固化可选项走 withXxx链式调用、顺序随意、不关心就跳过build() 产出不可变对象字段一次性注入后谁也别想改可安全共享。下次再看到构造函数里排着一排 null先别加重载想想是不是该写一个 Builder 了。【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址: https://gitcode.com/GitHub_Trending/ja/java-design-patterns创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑