这是 2020 年 3 月整理的设计模式专题第二篇。建造者模式是面试常客,尤其是”建造者和工厂有什么区别”这个问题。本文从定义出发,用 Computer 的链式 Builder 示例讲清实现,再深入对比它与工厂模式的本质差异。
简单说明
建造者模式(Builder Pattern):将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
- 用户只需要指定需要建造的类型就能得到它,建造细节无需关心;
- 建造者模式能根据方法的调用顺序不同和不同部件创建不同的产品;
- 而工厂模式创建的产品是一致的——这是两者最本质的区别。
工厂模式: 工厂 ——> 直接返回完整产品(固定配方)
建造者: 指挥者 ——> 一步步"指挥"建造者 ——> 按需组装出产品(可定制配方)
经典实现
| 经典实现 | 说明 |
|---|---|
| StringBuilder | 链式 append 组装字符串,最终 toString 输出 |
| Lombok @Builder | 注解自动生成 Builder 内部类 |
| Guava cache | CacheBuilder 链式配置构建缓存实例 |
简单代码示例
@Data
@AllArgsConstructor
public class Computer {
private String cpu;
private String gpu;
private String memory;
public static ComputerBuilder builder() {
return new ComputerBuilder();
}
public static class ComputerBuilder {
private String cpu;
private String gpu;
private String memory;
public ComputerBuilder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public ComputerBuilder gpu(String gpu) {
this.gpu = gpu;
return this;
}
public ComputerBuilder memory(String memory) {
this.memory = memory;
return this;
}
public Computer build() {
return new Computer(cpu, gpu, memory);
}
}
}
使用:
Computer computer = Computer.builder()
.cpu("i7-9700K")
.gpu("RTX 2080")
.memory("16G")
.build();
链式调用的秘密:每个 setter 方法返回 this(建造者自身),所以可以一路 .cpu().gpu().memory() 连写;最后的 build() 才真正创建产品对象。
为什么要用建造者模式
没有建造者时,复杂对象的创建通常面临两个困境:
困境一:构造器参数爆炸
// 6 个参数的构造器,调用时根本分不清谁是谁
new Computer("i7-9700K", "RTX 2080", "16G", "512G SSD", "Wi-Fi 6", "机械键盘");
困境二:可选项与不可变对象冲突
// JavaBean 风格:可读性差,且对象是可变的(中途可能被改)
Computer c = new Computer();
c.setCpu("i7-9700K");
c.setGpu("RTX 2080"); // 如果忘了 set memory,对象就不完整
建造者的解法:字段只在 build() 时一次性组装进产品,产品本身可以设计成不可变对象;同时每个字段通过语义化方法名(.cpu()、.gpu())显式赋值,可读性远超长构造器。
GoF 经典角色
完整的建造者模式包含四个角色:
| 角色 | 职责 | Computer 示例中的对应 |
|---|---|---|
| Product(产品) | 被构建的复杂对象 | Computer |
| Builder(抽象建造者) | 声明构建各部件的方法 | ComputerBuilder 的方法集合 |
| ConcreteBuilder(具体建造者) | 实现各部件的具体构建逻辑 | ComputerBuilder |
| Director(指挥者) | 定义构建步骤与顺序 | 本例中由客户端直接链式调用充当 |
什么时候需要 Director:当构建步骤有固定顺序(比如必须先装 CPU 才能装内存)且顺序被多处复用时,抽出 Director 统一编排;如果只是简单的可选字段组装,客户端链式调用即可,Director 可省略。
public class ComputerDirector {
private Computer.ComputerBuilder builder;
public ComputerDirector(Computer.ComputerBuilder builder) {
this.builder = builder;
}
// 固定的组装流程:CPU → 内存 → GPU
public Computer constructGamingPC() {
return builder.cpu("i7").memory("16G").gpu("RTX 2080").build();
}
public Computer constructOfficePC() {
return builder.cpu("i3").memory("8G").gpu("核显").build();
}
}
建造者 vs 工厂
| 对比项 | 工厂模式 | 建造者模式 |
|---|---|---|
| 关注点 | 创建什么产品 | 如何一步步组装产品 |
| 产品形态 | 创建的产品是一致的(同一种类) | 按调用顺序/部件不同,可创建不同的表示 |
| 创建粒度 | 一步创建,直接返回 | 分步构建,最后 build() |
| 适用场景 | 产品种类多、创建逻辑相似 | 对象构造复杂、参数多、有可选字段 |
| 典型实现 | Calendar、Spring BeanFactory | StringBuilder、Lombok @Builder |
一句话区分:工厂模式”造什么”,建造者模式”怎么造”。
使用场景与优缺点
适用场景:
- 对象的构造参数很多,且大部分可选;
- 创建过程步骤复杂、顺序有讲究(如 XML 解析、SQL 组装);
- 希望产品是不可变对象。
优点:
- 构建与表示分离,客户端无需关心组装细节;
- 链式调用可读性强,语义清晰;
- 可以精细控制构建过程,创建不同的产品表示;
- 产品可以设计为不可变对象,线程安全。
缺点:
- 需要额外编写 Builder 类,类数量增加;
- 如果产品简单、参数少,使用建造者是过度设计。
小结
- 建造者模式将复杂对象的构建与表示分离,同样的构建过程可以创建不同的表示。
- 核心区别:工厂创建一致的产品,建造者按需组装出不同表示的产品。
- 链式 Builder 通过 setter 返回
this实现,build()一次性组装。 - 经典应用:StringBuilder、Lombok
@Builder、GuavaCacheBuilder。 - 简单对象别用建造者,它是为”参数多、组装复杂”的对象准备的。