第059篇 建造者模式:链式调用为何无处不用在哪些地方

简介: 建造者模式核心是**将复杂对象的创建过程外置、分步可控、集中校验**。它解决参数过多、互斥依赖、分步初始化问题,关键在于:私有构造、链式setter、build()统一校验、产物不可变(final+防御拷贝)、默认值内聚于Builder——非仅为语法糖,而是工程化构造控制。

建造者模式在面试里常被问"和工厂有什么区别",但真正能区分开的是另一件事:建造者把"创建一个复杂对象的过程"从对象本身挪到了外部,并且要求这个过程分步可控、随时可校验。很多人以为它就是给构造函数拆参数,其实那只是表象;真正值钱的地方是"分步 + 校验 + 复用 + 不可变"这四件事。

先把结论放在前面:建造者模式(Builder)解决的是构造参数过多、参数组合爆炸、对象分步初始化三类问题。核心手法是把一个类拆成"不可变的构建者"与"最终产物"两半:构建者每个 setter 返回 this 支持链式调用,build() 时统一做参数校验并产出对象。 判断该不该用的标准很直接:构造函数超过 4 个参数、或者参数之间存在互斥与依赖关系、或者对象需要分多步才能填满。

机制拆解

以 Android 里的 AlertDialog.Builder 或网络请求构造器为例,看清每一步在解决什么。第一步,私有化带参构造,把构造函数藏起来,强制外部走构建者——这样就断掉了"有人直接 new 出一个参数不完整的对象"这条路径。第二步,字段在构建者与产物各存一份,构建者可改、产物只读;build() 里的 new 用构建者的当前值填充,产物此后不可变。 第三步,每个 setter 返回 this,让调用可以写成一行链式代码,可读性来自"一行只做一件事"的顺序表达。第四步,校验集中在 build(),也就是对象即将诞生的那一刻——此时所有参数都已就位,是做一致性校验的唯一正确时机。

第五步,默认值写在本类而非调用方。这一点是本篇标题的由来:默认值写在 Builder 里,是这个类自己的领域知识;写在每个调用点,就是把领域知识抄成了散落各处的重复代码。Android 里最典型的例子是 Retrofit 的 Builder、Glide 的 RequestOptions、以及各种 Dialog 参数默认值。

这些坑的正确绕法

最常见的坑是 build() 里没做必填校验,非法配置一路带到运行期深处才爆。 表现是空指针或下标越界出现在离赋值点很远的地方,堆栈看不出是哪个参数缺了。根因是构建过程被拆成多步后,校验如果分散在各 setter 里,会漏;即使不漏,也无法表达"这几个参数互斥、这几个必须同时出现"这种跨参数规则。 规矩是:必填校验、互斥校验、范围校验一律写在 build() 开头,失败要抛带明确信息的异常,异常信息里要写清是哪个参数不合法。

其次是把 Builder 建成可变又允许复用,同一个构建者产出两个"配置互相污染"的对象。 典型 bug 是先 new Builder().setA(1).build(),之后又 .setB(2).build() 一次,第二个对象里 a 也变成了 1 之外的预期值。根因是构建者自身被复用。 两种修法:要么每次 new 一个构建者(Android 里最常见),要么提供一个 copy()/不可变构建者;Java 里常见的 StringBuilder 之所以可复用,是因为它的每次 append 本身就是预期行为,而领域构建者不是。

还有一个更隐蔽的坑:给可变对象加了一个 Builder,但没有真正做不可变。 表现是 build() 之后仍能改产物的字段(集合没做防御性拷贝、字段不是 final),于是"不可变对象"名存实亡,跨线程共享时又冒出难以复现的问题。 修法是字段逐个 final、返回集合时用 Collections.unmodifiableList 或 Kotlin 的 toList() 做一次拷贝。

代码里见真章

看一段能直接跑的代码,把上面的机制落到具体写法上:

// Builder:私有构造 + 链式 setter + build() 集中校验 + 产物不可变
public final class Request {
   
    private final String url;             // 字段逐个 final,构造后不可变
    private final int timeoutMs;
    private final Map<String, String> headers;
    private final byte[] body;

    private Request(Builder b) {
            // 构造函数私有,外部只能走 Builder
        this.url = b.url;
        this.timeoutMs = b.timeoutMs;
        this.headers = Collections.unmodifiableMap(new HashMap<>(b.headers));  // 防御性拷贝
        this.body = b.body == null ? null : b.body.clone();
    }

    public static final class Builder {
   
        private String url;               // 领域默认值写在本类,不散落到调用方
        private int timeoutMs = 10_000;
        private final Map<String, String> headers = new LinkedHashMap<>();
        private byte[] body;

        public Builder url(String u) {
    this.url = u; return this; }   // 返回 this 支持链式
        public Builder timeoutMs(int t) {
   
            if (t <= 0) throw new IllegalArgumentException("timeout 必须 > 0");
            this.timeoutMs = t; return this;
        }
        public Builder header(String k, String v) {
    this.headers.put(k, v); return this; }

        public Request build() {
             // 校验集中在这里,一步到位
            if (url == null || url.isEmpty()) {
   
                throw new IllegalStateException("必填项 url 未设置");
            }
            if (url.startsWith("http://")) {
      // 互斥/约束规则只在这里能表达
                throw new IllegalStateException("仅允许 https: " + url);
            }
            if (body != null && body.length > MAX_BODY) {
   
                throw new IllegalStateException("body 超过上限 " + MAX_BODY);
            }
            return new Request(this);      // 一次性固化
        }
    }
    // 用法:顺序即语义,读起来像一句话
    Request r = new Request.Builder().url("https://api/x").header("A", "1").build();
}

这段代码值得盯三处:第一处,字段全 final + 集合防御性拷贝,让产物真正不可变;第二处,build() 里同时做必填、协议约束、体积上限三类跨参数校验——这些规则在 setter 阶段无法表达;第三处,默认值 timeoutMs = 10_000 定义在本类,调用方不用重复,也不必知道默认值是多少。

这题在面试里怎么问、怎么答

"建造者和工厂模式有什么区别?"分三层:①建造者分步、关注怎么拼;工厂一步到位,关注造哪一个;②建造者常用于同一类型有多种参数组合,工厂常用于多种类型的选择(此时建造者常被用作工厂内部实现);③建造者产出的是同一种类型,工厂产出的是接口下的不同实现。 Android 上的例子:Retrofit.Builder 是建造者(拼接口实现与配置),ListAdapter 的多类型工厂是工厂。

"建造者什么时候反而是负担?"给判据:参数只有两三个且互不相关时,建造者只是多写三倍代码;对象需要分步创建、中途可能被访问时,建造者隐藏了中间态会让人困惑("到底哪一步之后才算有效");不可变要求不强时,它带来的 final 与拷贝成本没有回报。

"和 Kotlin 的命名参数/default 参数相比呢?"Kotlin 的 data class + 默认参数 + 具名调用在很多场景下更简洁,不必写 Builder。差别在三点:Kotlin 默认参数不适用于 Java 调用方、参数超过十几个时可读性也会崩、且默认参数无法表达"参数组合互斥"这类跨参数规则。 Android 里给 Java 调用的组件库(第三方 SDK)仍要提供 Builder。

"使用中遇到过什么问题?"案例一:某个配置类允许 setA 与 setB 同时设置,但两者冲突时运行期抛错;定位到校验分散在各 setter,只查了单参数合法性;修复为把互斥规则集中到 build() 并补上组合参数的单测。案例二:一个 Builder 被复用来造两个不同配置的请求,第二个请求带上了第一个的残留值;修复为每次新建构建者,并把返回集合改为防御性拷贝。

再补一个工程上值得讲清的点:Android 上的对话框、通知、请求构造几乎都是 Builder,它们的默认值就是"领域知识的沉淀处"。 举例:如果播放器的默认缓冲策略、默认超时、默认重试次数写在 SDK 的 Builder 里,业务方只在特殊场景覆盖;当产品同学要改默认值时只改一处,且升级 SDK 后新默认值自动生效。 反之如果默认值散在十几个业务页面的构造代码里,改一次要翻十几个文件,还容易漏。这是建造者模式在工程上最实在的价值——比"链式调用好看"重要得多。

给正在准备面试的你

把建造者画成一张"分步固化"的示意图:左边一列是构建者里的可变状态(url、timeout、headers,箭头指向每一步的 setter),右边一列是产物里的 final 字段。中间画一个大箭头标 build(),箭头上写三行字——必填校验、跨参数约束、一次性固化。产物那边画一个锁形图标标"不可变",并注明"集合做防御性拷贝"。 再在图下方写一句判据:"参数 > 4、参数间有约束、需要分步填满"三条满足两条就用 Builder。面试中建造者的问题,基本都能从这张图推导出答案。

再补工程案例与踩坑——应用落点是把项目里参数超过四个的构造调用统一改走 Builder,把 build() 前的校验集中化并补上"缺必填、传互斥值、超范围"三条单测,同时检查所有返回集合的地方是否做了防御性拷贝。

复习时别孤立刷题:单例模式六种写法——单例解决唯一性、建造者解决构造过程的可控性,两者都在解决"对象的诞生方式"。

划两句重点:私有构造 + 链式 setter + build() 集中校验 + 产物不可变(final + 防御性拷贝);默认值写在 Builder 里是领域知识;构建者不可复用,产物不可变。

下一篇聊 Java 阶段面试通关地图:六十个考点串联复盘——沿着今天这条主线继续往前走。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。你的支持就是这个系列持续更新的动力。

「Android软件开发面试·从入门到精通」连载系列

上一篇:单例模式六种写法:线程安全与懒加载的平衡

下一篇预告:Java-阶段面试通关地图:六十个考点串联复盘

有任何问题欢迎在评论区留言交流。

相关文章
|
17天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
8478 24
|
16天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
2817 14
|
15天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2021 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
14天前
|
人工智能 编解码 并行计算
MiniMax-H3 一键整合包技术文档:8G 显存运行 AI 漫剧制作 —— 角色替换 / 动作迁移 / 文图生视频部署与调参指南
MiniMax H3 是 MiniMax 开源的全模态视频生成模型,支持文/图/音/视多条件输入,输出最高2K、15秒带双声道音频视频。本文档详述其Int8量化版在8GB显存下的本地一键部署、三段式工作流(EDIT/REPLACE/CONTINUE)、参数调优及常见问题排查。(239字)
|
10天前
|
人工智能 Linux 开发者
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
Codex是OpenAI推出的AI编程智能体,可读取本地项目、理解需求并自动修改代码。支持桌面GUI、命令行(CLI)及VS Code/Cursor插件三种形态,覆盖可视化操作、终端高效开发与编辑器无缝集成场景,助开发者用自然语言驱动编码全流程。(239字)
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
|
4天前
|
人工智能 JSON Linux
【全网最详细】ComfyUI使用教程:下载+本地部署+配置+工作流搭建一篇搞定(2026最新版)
ComfyUI是一款免费开源的本地AI绘图工具,采用节点式工作流设计,支持文生图、图生图、局部重绘、放大、换脸等多种功能。可离线运行,依赖显卡加速,无需联网。支持自定义流程保存与分享,插件生态丰富,适合进阶用户。(239字)
|
10天前
|
人工智能 JSON 编解码
【2026最新版】ComfyUI本地部署教程,新手也能看懂!
ComfyUI是本地运行的AI绘画工具,采用节点式工作流设计:通过拖拽连接“加载模型”“提示词编码”“采样”“解码”等模块,实现高度可控的文生图。新手推荐使用秋叶整合包,一键启动、内置模型管理与插件安装器,轻松上手。(239字)

热门文章

最新文章