CS-Notes 设计模式精讲生成器模式Builder与 JDK StringBuilder 的按步骤构造实现【免费下载链接】CS-Notes:books: 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Notes生成器模式Builder Pattern是一种创建型设计模式核心思想是封装一个对象的构造过程并允许按步骤构造。本篇文章以 CS-Notes 中《设计模式 - 生成器》笔记为主线结合笔记给出的参考 JDK 1.8 源码的简易 StringBuilder 实现逐行拆解append链式调用的底层机制、容量扩容与防御性拷贝等细节帮助读者在面试与工程实践中真正理解 Builder 模式分层构造、逐步装配的精髓读完即可独立复现并可迁移到自己的 Java 项目中。生成器模式是什么把对象构造过程封装成可编排的步骤原文 Intent封装一个对象的构造过程并允许按步骤构造。这句话可以拆成两个层次来理解封装构造过程对象的创建不再由客户端直接拼接一堆构造参数而是交给一个独立的生成器对象让它专职负责内部复杂逻辑的装配客户端只需要按语义发出构造指令按步骤构造生成器对外暴露细粒度的步骤方法客户端或导演者 Director可以把这些步骤排列组合分阶段地构建出一个完整产品从而让构造过程本身成为可变、可编排、可复用的逻辑。该模式在 CS-Notes 的设计模式目录中被归入二、创建型分类与单例、简单工厂、工厂方法、抽象工厂、原型模式并列。创建型模式的共性是将对象创建逻辑从客户端解耦生成器的独特之处在于它处理的是构造过程复杂、需要分多步完成的对象而不是一步到位地返回实例。相比直接用构造函数传参生成器模式最大的收益是当产品有大量可选属性、参数类型相近容易传错顺序、或对象希望保持不可变字段在构建完成后再不修改时Builder 能以语义清晰的链式调用消除这类问题同时把默认值处理与参数校验集中收敛在生成器内部。模式结构总览Director / Builder / ConcreteBuilder / Product 四个角色Class DiagramCS-Notes 笔记中的生成器模式类图位于仓库图片资源 notes/pics/db5e376d-0b3e-490e-a43a-3231914b6668.png图示如下按图所示标准的生成器模式通常包含四个角色角色职责Product产品被构建的复杂对象内部通常有多个组成部分由 ConcreteBuilder 逐段填充Builder抽象生成器声明构建产品各个部分的抽象方法如类图中的buildPart()ConcreteBuilder具体生成器实现 Builder 接口真正完成每个部件的构建并额外提供getResult()返回最终产品Director指挥者面向 Builder 抽象编程在construct(Builder)中按既定顺序调用buildPart()从而控制整个装配流程它们之间有三类关键关系Director依赖虚线箭头指向Builder抽象因此它只知道怎么编排步骤不关心每一步由哪个具体生成器执行天然满足开闭原则——新增一种 ConcreteBuilder 无需改动 DirectorConcreteBuilder通过继承/实现空心三角箭头继承自BuilderConcreteBuilder与Product是关联关系构建完成后通过getResult()把产品交给调用方。类图左下角的注释for all objects in structure: builder.buildPart();点明了 Director 的核心动作遍历待构建部件集合对每个部件调用一次buildPart()从而把步骤编排与部件实现彻底分离——这正是生成器按步骤构造的结构化表达。实现案例用简易 StringBuilder 复现按步骤构造原文 Implementation 说明以下是一个简易的 StringBuilder 实现参考了 JDK 1.8 源码。StringBuilder 是整个 Java 生态中最典型的按步骤构造字符串的类每次append(...)只追加一小段内容多次追加逐步拼接出完整字符串这与 Builder 的 Intent 完全吻合。笔记用三个类还原了 JDK 的实现骨架AbstractStringBuilder持有底层字符数组与扩容逻辑public class AbstractStringBuilder { protected char[] value; protected int count; public AbstractStringBuilder(int capacity) { count 0; value new char[capacity]; } public AbstractStringBuilder append(char c) { ensureCapacityInternal(count 1); value[count] c; return this; } private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length 0) expandCapacity(minimumCapacity); } void expandCapacity(int minimumCapacity) { int newCapacity value.length * 2 2; if (newCapacity - minimumCapacity 0) newCapacity minimumCapacity; if (newCapacity 0) { if (minimumCapacity 0) // overflow throw new OutOfMemoryError(); newCapacity Integer.MAX_VALUE; } value Arrays.copyOf(value, newCapacity); } }在这个抽象生成器中char[] value是实际存储字符的缓冲数组int count记录已写入的有效字符个数而不是数组长度构造方法接收capacity一次性分配底层数组避免每次追加都新建数组append(char c)先通过ensureCapacityInternal(count 1)确认容量足够再value[count] c写入字符最后return this返回自身——这就是链式调用的实现根基expandCapacity负责在空间不足时整体扩容详见下文扩容算法小节。StringBuilder具体的字符串构造器public class StringBuilder extends AbstractStringBuilder { public StringBuilder() { super(16); } Override public String toString() { // Create a copy, dont share the array return new String(value, 0, count); } }作为 AbstractStringBuilder 的子类StringBuilder 默认以16为初始容量调用父类构造toString()则把当前已写入的count个字符拷贝成一个新 String详见下文防御性拷贝小节。Client逐步追加并输出结果public class Client { public static void main(String[] args) { StringBuilder sb new StringBuilder(); final int count 26; for (int i 0; i count; i) { sb.append((char) (a i)); } System.out.println(sb.toString()); } }客户端先new StringBuilder()得到一个初始容量 16 的空生成器然后在循环中26 次逐步 appenda到z最后一次toString()输出完整字符串abcdefghijklmnopqrstuvwxyz这个例子直观地演示了 Builder 的使用形态构造不是一条命令创建出成品而是反复追加、逐步成型、最后统一取出。值得注意的是本例中省略了类图中的 Director 角色由 Client 直接驱动生成器——当装配流程简单或调用方本身愿意承担编排职责时这是 Java 生态中最常见的链式/流式 Builder 变体属于对经典 GoF 结构的一种务实简化。深入拆解 StringBuilder 的三个关键实现细节这一节对笔记给出的三段代码做逐层剖析它们同时也是高频面试点。1.return this是链式按步骤构造的地基在AbstractStringBuilder.append末尾代码返回this使每一次追加后的结果就是生成器自身因而可以连续书写new StringBuilder() .append(a).append(b).append(c) // 每步返回自身串成一条链 .toString();这在工程上被称为fluent API / 流式接口每个步骤方法返回Builder自身调用方无需持有中间变量即可按步骤拼装。相比在构造函数里一次性塞入所有参数这种形态让每一步都有独立的语义和方法名可读性显著提升也是 LombokBuilder、各类测试数据构造器等工具普遍采用的原因。2. overflow-conscious 的容量检查与动态扩容算法容量管理是 StringBuilder 性能与正确性的核心笔记代码中两处细节非常典型a)ensureCapacityInternal用减法代替直接比较if (minimumCapacity - value.length 0) // 而不是 minimumCapacity value.length expandCapacity(minimumCapacity);注释明确写着// overflow-conscious code。当字符串长度逼近Integer.MAX_VALUE时minimumCapacity count 1这类加法可能溢出为负数把容量比较改写成差值形式可以让判断逻辑在极端边界下仍保持正确是 JDK 对整数溢出的一种防御性写法。b)expandCapacity的三级兜底int newCapacity value.length * 2 2; // ① 按当前长度×22进行指数扩容 if (newCapacity - minimumCapacity 0) newCapacity minimumCapacity; // ② 扩容后仍不够直接取所需最小值 if (newCapacity 0) { // ③ ×22 溢出成负数时 if (minimumCapacity 0) // 连所需容量都溢出了视为真溢出 throw new OutOfMemoryError(); newCapacity Integer.MAX_VALUE; // 否则封顶到 int 最大值 } value Arrays.copyOf(value, newCapacity);扩容按约 2 倍增长使多次 append 的平均拷贝代价趋于 O(1)摊还分析意义上的高效同时每次newCapacity - minimumCapacity 0的比较同样采用防溢出的差值写法当数组长度过大导致value.length * 2 2溢出为负值时若连minimumCapacity也已溢出则抛出OutOfMemoryError终止说明对象已不可能在 Java 内存边界内构建完成若仅是扩容值溢出、所需容量仍合法则把容量封顶到Integer.MAX_VALUE扩容通过Arrays.copyOf(value, newCapacity)将原数组内容整体搬入更大的新数组注意该类需要java.util.Arrays的引入。这一整套先检查、再扩容、最后拷贝的流程把数组越界风险、整数溢出风险与内存边界风险都处理得清清楚楚是研究 JDK 编码风格的绝佳入门样本。3.toString()的防御性拷贝不共享可变数组// Create a copy, dont share the array return new String(value, 0, count);toString()没有直接把内部value数组暴露给外部而是通过new String(value, 0, count)拷贝一份。原因很直接value是生成器内部仍可继续被append修改的缓冲数组若直接共享引用那么调用toString()之后再向生成器追加内容就会污染此前已产出的字符串。拷贝语义保证了 String 的不可变性也避免调用方意外修改到生成器内部状态——这是构建期可变、成品不可变这一 Builder 思想在编码层面的落地。Builder 在 JDK 与开源生态中的典型身影笔记末节以 JDK 为标题列出了一批使用该思想的真实类它们都可以作为理解生成器模式的对照样本java.lang.StringBuilder / java.lang.StringBuffer字符串按步骤构造的标准范例StringBuffer 在同步语义上做了额外处理二者都实现了java.lang.Appendable接口接口中的append方法族同样声明为返回Appendable自身以支持链式追加java.nio.ByteBuffer缓冲区写入类其put(...)等方法返回 Buffer 自身从而支持连续写入的链式调用是按步骤往缓冲里装配数据的另一个 JDK 级实现java.lang.Appendable声明了append系列方法的抽象契约凡是能够被逐步追加内容的对象如 StringBuilder、StringBuffer都遵循同一套追加并返回自身的约定Apache Camel其 camel-core 模块中提供了专门的 builder API 包org.apache.camel.builder以流式/链式方式逐步组装路由、端点等复杂对象是把 Builder 思想大规模应用于框架 DSL 设计的典型案例。从中可以看出一个规律凡是对象内容需要多次追加、分步设置且希望调用代码连续可读的场景生成器/流式 Builder 都是自然的选择这正是该模式在 JDK 中高频出现的根本原因。何时使用生成器模式适用场景与要点回顾结合笔记代码与模式结构可以将 Builder 的适用信号归纳如下构造过程包含多个步骤对象需要分阶段、按顺序装配且装配顺序可能变化此时引入 Director 编排更佳构造参数众多或易混大量同类型参数会导致构造函数可读性差、传参易错Builder 用方法名消除歧义希望产品构建完成后保持稳定构建期间数据可变、构建结束后整体导出如toString()拷贝语义能天然支持不可变产品的设计客户端需要清晰的链式 API像 StringBuilder 的append、ByteBuffer 的put那样让调用代码自解释。在做面试对比时可以抓住生成器与工厂类模式的差别工厂模式简单工厂、工厂方法、抽象工厂关心创建出哪一种产品通常一次调用直接返回实例生成器模式关心如何一步步把复杂产品装配出来构造过程本身是显式、可分步、可编排的。若产品简单到一条new或一次工厂调用就能完成就不必引入 Builder。本仓库中更多创建型模式的笔记单例、简单工厂、工厂方法、抽象工厂、原型模式可对照阅读生成器这一章也完整收录于设计模式总笔记 notes/设计模式.md 的第 5 节。建议在理解后亲手把上面的三段代码敲一遍并跑出abcdefghijklmnopqrstuvwxyz再尝试为一个拥有多个必选/可选字段的不可变类手写一个含 Director 与多个 ConcreteBuilder 的完整版本即可真正把生成器模式内化为自己的知识储备。【免费下载链接】CS-Notes:books: 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Notes创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考