周日晚上,把电车推到楼下充电。
天冷了,电池掉得快。以前三五天充一次,现在隔一天就得充一回。充满,周一早上骑到地铁站,晚上再骑回来,心里才踏实。
回屋翻衣柜。周一得穿厚一点。薄外套收进去,把羽绒服拽出来,袖口还留着上一个冬天的褶。
顺手把围巾和手套翻出来,搁在椅背上。
周一早上闭着眼也知道:先穿秋衣,再穿毛衣,最后套羽绒服;围巾塞进领口,手套戴上,背包甩到肩上。这个顺序一乱,穿上就别扭------先背包再套外套,背带勒在衣服里,取东西还得先把外套掀起来。
一件件穿,跟代码里造一个复杂对象,是一回事。

天冷了,出门前得一层层配
今天聊的生成器模式(Builder,也叫建造者模式):把一个复杂对象的装配过程拆成一步一步,每步只干一件事,最后一次性交出成品。系列第十六篇。
01 先看"一口气把参数塞进去"的办法
最直接的写法,是把这身衣服做成一个构造器,参数一次传齐。
java
class Outfit {
private final String base;
private final String mid;
private final String outer;
private final List<String> extras;
Outfit(String base, String mid, String outer, List<String> extras) {
this.base = base;
this.mid = mid;
this.outer = outer;
this.extras = List.copyOf(extras);
}
void describe() {
System.out.println("打底:" + base);
System.out.println("中层:" + mid);
System.out.println("外层:" + outer);
System.out.println("配件:" + (extras.isEmpty() ? "无" : String.join("、", extras)));
}
}
四个参数,一次传齐
调用:
java
Outfit commute = new Outfit(
"纯棉秋衣", "加绒毛衣", "短款羽绒服",
List.of("针织围巾", "防风手套"));
能穿,但看不出哪个是哪个
第一眼看上去还行。但三个 String 挨在一起,mid 和 outer 传反了,编译器一声不吭。哪天想在中间加一层"马甲",构造器的参数列表一改,所有 new Outfit(...) 的地方都得跟着改。
再往后,这身衣服可能还要带"是否防水""是否带帽",参数越堆越长。调用处变成一串看不出含义的字面量,写的人得对着构造器数位置。
穿衣服的人不这么干。没人早上站在衣柜前背参数顺序,是一件一件往身上套。
02 把穿衣拆成几步
把这个过程搬到代码里,就是 Builder。
先让每一步都有一个名字:
java
class OutfitBuilder {
private String base;
private String mid;
private String outer;
private final List<String> extras = new ArrayList<>();
OutfitBuilder base(String layer) {
this.base = layer;
return this;
}
OutfitBuilder mid(String layer) {
this.mid = layer;
return this;
}
OutfitBuilder outer(String layer) {
this.outer = layer;
return this;
}
OutfitBuilder extra(String item) {
extras.add(item);
return this;
}
}
每一步只存一样东西
每个方法返回 this,所以能一路点下去。
调用变成:
java
Outfit commute = new OutfitBuilder()
.base("纯棉秋衣")
.mid("加绒毛衣")
.outer("短款羽绒服")
.extra("针织围巾")
.extra("防风手套")
.build();
读起来就是穿衣顺序
打底、中层、外层、配件。哪个参数是什么,方法名自己说了。想在中间加一层,加一个方法就行,已经在用的调用处不用动。
03 build 的时候,检查一遍
build() 是最后那一下:把攒着的字段交出去,生成一个成品。
java
Outfit build() {
if (base == null) {
throw new IllegalStateException("打底不能少");
}
if (outer == null) {
throw new IllegalStateException("外套不能少");
}
return new Outfit(base, mid, outer, extras);
}
出门前看一眼,缺什么直接拦下
这一步构造器里也能写,但 Builder 把"攒"和"交"分开,校验放到最后一步更自然。秋天能少穿一层,冬天不行------打底和外套是必填,中间层和配件可有可无。 这些规则写进 build(),就不会出现"只穿着毛衣就出门"的对象。
成品 Outfit 的字段是 final,构造器没有对外开放。build 出来就是这身,外面改不了。
04 同一套步骤,配出不同组合
周一要骑电车,风大,手套得戴上,围巾得围好。换成不那么冷的日子,同一套步骤又能配出另一身:
java
Outfit mildDay = new OutfitBuilder()
.base("纯棉秋衣")
.mid("薄羊毛衫")
.outer("呢子大衣")
.build();
再试一次只给中层就 build():
java
try {
new OutfitBuilder().mid("加绒毛衣").build();
} catch (IllegalStateException e) {
System.out.println("拦截:" + e.getMessage());
}
两身都 describe() 一遍,连同刚才被拦下的那次,运行结果:
text
打底:纯棉秋衣
中层:加绒毛衣
外层:短款羽绒服
配件:针织围巾、防风手套
打底:纯棉秋衣
中层:薄羊毛衫
外层:呢子大衣
配件:无
拦截:打底不能少
同一套流程,配出两身
如果每天早上都是同一套顺序,还可以再抽一层指挥者(Director),把顺序写死:
java
class CommuteOutfit {
static Outfit build() {
return new OutfitBuilder()
.base("纯棉秋衣")
.mid("加绒毛衣")
.outer("短款羽绒服")
.extra("针织围巾")
.extra("防风手套")
.build();
}
}
调用方只说一句 CommuteOutfit.build()。这就是"闭着眼穿的那套"。
但别急着抽。只有一套固定组合时,new OutfitBuilder()...build() 写在调用处已经够清楚,再加一层指挥者,只是把代码挪了个地方。
05 这套穿法,什么时候别用
先把它和前面两个容易混的分开。
装饰器(Day 5)是"穿好之后还能一层层加" :骑电车冬天加挡风被、加加热把套,是运行时往上套,随时能拆。生成器是"装配的时候一层层配",build 完就定型。
拿穿衣打比方,装饰器是到公司热了脱外套、出门冷了再穿上;生成器是出门前把这身配好,配好就不再变。
工厂(Day 2)回答的是"造哪一种",生成器回答的是"这一种怎么一步步造出来"。 点餐时说"来个麦辣鸡腿堡",是工厂;这堡的面包、肉、酱怎么一层层码起来,是生成器。工厂管结果类型,生成器管装配过程。
然后是别用的地方。
字段就两三个,构造器直接 new 更省事。 为了两个参数造一个 Builder 类,纯属多一层。
装配没有顺序、没有必填项,Builder 的校验也用不上。 那它就只是一段更长的构造器调用。
还有一处失真得挑明:现实里衣服穿错了能脱下来重穿,代码里 Builder 的步骤顺序乱了,如果不校验,build 出来的对象照样能跑 ------只是这身衣服不合逻辑。所以"顺序约束"不是白送的,得自己写进 build() 里。
06 周一早上,出门
闹钟响的时候,天还没亮透。
秋衣、毛衣、羽绒服,一件件套上。拉链拉到顶,围巾裹严实,手套戴上,背包甩上肩。
电车昨晚充满了,拧开钥匙,电量满格。
天冷是真的冷。但这身是一层层配出来的,哪层该在里头,哪层该在外头,出门前就想清楚了。

装配归 Builder,成品归 Outfit,中间那一步 build 负责收口
跟前面十五篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管这一步用什么做法,装饰器管外面加几层,命令管把请求做成对象,代理管谁能碰到真东西,观察者管变化后通知谁,外观管一堆零件只留一个入口,状态管当前状态下怎么做,责任链管请求由哪一环接住,原型管拿哪个现成对象复制,桥接管抽象和实现怎么独立扩展,享元管把能共享的部分只留一份,备忘录管把某个时刻的状态存下来、再原样恢复。本篇生成器,管把复杂对象的装配拆成一步一步,最后一次性成型。
带走一句话:生成器 = 把复杂对象的装配拆成一步一步,每一步只管一件事,最后一次性成型。
衣服一件件穿,日子一天天过。
周一就周一吧。
参考资料
- 《设计模式:可复用面向对象软件的基础》(GoF 原著)------生成器模式的原始定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
- Refactoring Guru · Builder------生成器模式的结构:Builder、ConcreteBuilder、Director、Product 的分工
- 《Effective Java》第 2 条:遇到多个构造器参数时要考虑使用构建器------构造器参数过多时,用 Builder 替代重叠构造器和 JavaBean 写法