你的构造参数越写越长——不是类设计错了,是该用 Builder 模式了
2026/8/14 9:14:04 网站建设 项目流程

我见过一个类,构造函数长这样:

java public OrderQuery( Long userId, Integer status, Date startTime, Date endTime, String keyword, Integer page, Integer pageSize, String sortField, Boolean desc, List<Long> tagIds ) { // ... }

十个参数,还不算最坏的情况。调用的时候更痛苦:

java new OrderQuery(userId, null, null, null, "手机", 1, 20, "createTime", true, null);

你盯着这行代码,根本不知道哪个 null 对应什么。这种代码不是业务复杂,是构造方式出了问题。

构造函数参数爆炸是怎么来的

参数多通常有两种原因。一种是对象本身确实需要很多状态,比如查询条件、配置对象。另一种是设计者把"可选"和"必须"混在一起,全部塞进构造函数。

后者的危害更大。因为构造函数要求调用者按固定顺序填值,少一个就编译不过。为了应对可选参数,团队会开始写重载:

java public OrderQuery(Long userId) public OrderQuery(Long userId, Integer status) public OrderQuery(Long userId, Integer status, Date startTime)

这就是经典的 telescoping constructor 反模式。每增加一个可选参数,重载数量可能翻倍。代码没变薄,反而更难维护。

Builder 模式解决的不是"参数多"

很多人以为 Builder 模式就是为了让链式调用好看。这个理解太浅。

Builder 真正解决的是命名调用渐进构造的问题。

命名调用意味着你不再靠位置猜参数,而是靠名字:

java OrderQuery query = OrderQuery.builder() .userId(userId) .keyword("手机") .page(1) .pageSize(20) .sortField("createTime") .desc(true) .build();

每一个.xxx()都是自解释的。你不需要回到类定义里数参数位置。

渐进构造意味着对象可以分阶段组装。有些字段现在知道,有些字段稍后填充。这在构造复杂对象时特别重要,比如一个 HTTP 请求对象,header、body、timeout 可能来自不同的地方。

但 Builder 不是万能药

我见过有人给只有两三个必填参数的类也加 Builder。那是过度设计。

Builder 适合的场景有三个信号:

  • 可选参数超过三个,且组合方式多变;
  • 对象构造完成后应该不可变,不允许后续 setter 修改;
  • 构造逻辑复杂,需要校验或转换。

如果只是一个简单的值对象,两三个字段,直接写构造函数加 final 字段就够了。不要为了炫技引入 Builder。

另一个常见误区是把 Builder 当成 Factory 用。Factory 解决的是"创建哪个具体类"的问题,Builder 解决的是"同一个类怎么一步步组装"的问题。前者做选择,后者做装配。

一个真实的踩坑:Builder 里的校验不要拖到 build() 里才发现

有次我们用一个 Builder 构造消息通知对象。必填字段是templateIdreceiverId,但 Builder 没有在设置时校验,只是拖到build()方法里统一检查。

结果一个粗心的同事在链式调用里漏了.receiverId(),代码编译通过,运行到build()才抛异常。更糟的是,这个对象是在异步任务里构造的,异常被吞掉,消息根本没发出去。

后来我总结了一条规则:必填字段在 Builder 里用强制方法体现,可选字段用带默认值或延迟校验。如果某个字段真的缺一不可,就在build()里抛IllegalStateException,并且异常信息要告诉用户缺了哪个字段。

现代 Java 里的 Builder:手写还是 Lombok?

Lombok 的@Builder很方便,但有个坑——它默认把所有字段都放进 Builder,包括那些你本来想隐藏的字段。

比如一个领域对象有idcreatedAt两个数据库自动填充的字段。如果直接用@Builder,调用方可能会写.id(123L),构造出一个违反业务规则的对象。

我们的做法是:在领域对象上不用@Builder,而是在需要构建的场景里写一个专门的 Builder 类或静态工厂,只暴露允许外部设置的字段。Lombok 适合 DTO、查询条件这类扁平对象,不适合领域模型。

Builder 模式的本质

Builder 模式把"对象是什么"和"对象怎么造出来"拆开了。

构造函数表达的是对象最终状态:你给我这些参数,我给你一个完整对象。Builder 表达的是构造过程:你可以分步骤、按名字、按需要组合。

当你的对象构造过程本身就有复杂度时,就该考虑 Builder。否则,你写的不是构造逻辑,是在跟编译器和调用者打游击。

我在做一个用卡皮巴拉讲设计模式的微信小程序「爪爪代码冒险记」,23 个设计模式用漫画 + 答题的方式讲,目前正在开发中。如果你觉得这类内容有意思,搜一下「爪爪代码冒险记」,或者等我后面的文章。

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

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

立即咨询