前几天看电脑配置单,我盯着一串参数看了半天。
CPU、内存、硬盘还好,后面再跟个显卡、散热、机箱,眼神稍微一飘,就不知道哪个值该放哪儿了。
Java 里也常碰到这种情况。今天咱就拿狗哥组装电脑来说说 建造者模式。

先看一个有点别扭的写法
假设一台电脑有四项配置:CPU、内存、硬盘、显卡。
直接用构造方法,大概会写成这样:
Computer computer = new Computer(
"i5", "16GB", "1TB", null
);
代码能跑,但读起来不太舒坦。
"16GB" 是内存还是硬盘?最后那个 null 又代表啥?过两天再回来看,还得翻构造方法。
参数再多一点,顺序还容易传错。俩参数的小类没啥,七八个参数就真有点拧巴了。
换成一张“配置单”
电脑店装机,一般不会让你背着顺序报参数。
配置单上写清楚:CPU 选 i5,内存 16GB,硬盘 1TB。显卡暂时不要,也不影响先把机器装起来。
Builder 干的活,跟这张配置单差不多。
这里就两个角色:
Builder暂存每一步配置;Computer保存最后装好的结果。
先别背定义。看完代码,这俩角色自然就对上了。
最小版 Builder
先把 Computer 的字段定下来。电脑一旦装好,配置就不再跟着 Builder 变化,所以字段用 final。
public final class Computer {
private final String cpu;
private final String memory;
private final String disk;
private final String graphicsCard;
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.memory = builder.memory;
this.disk = builder.disk;
this.graphicsCard = builder.graphicsCard;
}
}
构造方法写成 private,外面不能随手 new Computer(...)。
想装电脑,统一找 Builder。
public static final class Builder {
private String cpu;
private String memory;
private String disk;
private String graphicsCard = "none";
public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder memory(String memory) {
this.memory = memory;
return this;
}
public Builder disk(String disk) {
this.disk = disk;
return this;
}
public Builder graphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
return this;
}
}
每个方法只做一件小事:记住一项配置,然后把当前 Builder 返回去。
所以调用时可以一段接一段地写。
Computer computer = new Computer.Builder()
.cpu("i5")
.memory("16GB")
.disk("1TB")
.build();
跑出来是:
CPU=i5, memory=16GB, disk=1TB, graphicsCard=none
这段比四个裸参数好认。想改内存,就找 .memory();想加显卡,就补一行 .graphicsCard("RTX 4060")。
不过有个地方容易说偏:链式调用不等于建造者模式。
真正要紧的是,Builder 先收集配置,最后再创建完整对象。链式写法只是让代码顺手些。
必填项放到 build() 里检查
电脑可以不装独立显卡,但不能没有 CPU、内存和硬盘。
这三项统一在 build() 里检查,少一项就不让出货。
public Computer build() {
require(cpu, "CPU is required");
require(memory, "memory is required");
require(disk, "disk is required");
return new Computer(this);
}
private static void require(String value, String message) {
if (value == null || value.isBlank()) {
throw new IllegalStateException(message);
}
}
比如漏掉 CPU:
new Computer.Builder()
.memory("16GB")
.disk("1TB")
.build();
程序会抛出:
java.lang.IllegalStateException: CPU is required
校验集中在一个地方,调用的人不用猜规则。这个小细节挺实用,省得半成品对象混进后面的代码。
build() 之后,电脑已经独立了
build() 每次都会创建一个新的 Computer。
后面继续修改 Builder,不会把已经装好的那台电脑偷偷改掉。
Computer.Builder builder = new Computer.Builder()
.cpu("i5")
.memory("16GB")
.disk("1TB");
Computer first = builder.build();
Computer second = builder.cpu("i7").build();
System.out.println(first.getCpu()); // i5
System.out.println(second.getCpu()); // i7
第一台还是 i5,第二台才是 i7。
这里靠的是 Computer 从 Builder 里复制字段,而且自身字段不能再改。济南话说,就是各是各的,别掺和。
啥时候适合用
Builder 不是见个类就往上套。
如果一个类只有姓名、年龄两个字段,普通构造方法就挺清楚,没必要多写一层。
我一般会看这几件事:
- 参数已经多到不好认;
- 有些必填,有些可选;
- 创建前需要统一校验;
- 希望最终对象创建后别再乱改。
碰上其中两三条,Builder 往往就比较合适。要是只有两个简单参数,咱别整复杂了。
狗哥收个尾
这篇代码不长,核心就是 先配,后建。
Builder 负责记配置,build() 负责检查并创建对象,Computer 只保存最后结果。
你可以把示例里的电脑换成订单、请求参数或导出任务,再敲一遍。字段一多,手感很快就出来了。
下一篇接着聊 原型模式。看看已经有一个对象时,怎么从它手里再复制一个。