建造者模式 (Builder) —— 乐高积木怎么搭
前言
在深入探讨建造者模式之前,想向大家推荐一个非常棒的开源项目:design-patterns-23。这个项目用最现代的技术栈重写了 23 种设计模式,非常适合实战学习,还配套了在线交互演示站,可以边读文章边动手玩。本文的实战案例灵感也来源于此。
1. 建造者模式是什么?
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
通俗解释:
- 组装电脑:你需要选 CPU、显卡、内存、硬盘、电源、机箱。而且安装顺序有讲究(先把 CPU 装主板上,再装风扇)。
- 肯德基点餐:汉堡 + 薯条 + 可乐 = 套餐 A。虽然都是这几样东西,但组合方式不同,结果就不同。
1.1 为什么我们需要它?
核心痛点:构造函数参数过多(伸缩构造函数反模式)。
假设我们要创建一个 Computer 对象,它有几十个参数。 烂代码示例:
public class Computer {
public Computer(String cpu, String gpu, int ram, int storage, boolean hasBluetooth, boolean hasWifi, String os, ...) {
// ...
}
}
// 客户端调用
Computer pc = new Computer("Intel i9", "RTX 4090", 32, 1024, true, true, "Windows 11", ...);问题:
- 参数太难记:第 5 个参数是蓝牙还是 WiFi?传错了编译器又不报错(都是 boolean),运行时才发现 bug。
- 可选参数处理麻烦:如果我不需要蓝牙,我不仅要传
false,甚至可能要写多个重载的构造函数。 - 可读性极差:这一行代码写出来,没人知道那一堆
true,false,null到底代表什么。
2. Java 实战演练:组装高配电脑
我们要创建一个 Computer 类,核心配置(CPU, RAM)是必填的,其他配置(蓝牙, WiFi)是选填的。
2.1 目标类 (Computer)
注意:我们将构造函数私有化,强制通过 Builder 来创建。
public class Computer {
private String cpu; // 必填
private String ram; // 必填
private int usbCount; // 选填
private String keyboard; // 选填
private String display; // 选填
// 私有构造函数,参数是 Builder
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.usbCount = builder.usbCount;
this.keyboard = builder.keyboard;
this.display = builder.display;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", usbCount=" + usbCount +
", keyboard='" + keyboard + '\'' +
", display='" + display + '\'' +
'}';
}
// 静态内部类 Builder
public static class Builder {
private String cpu; // 必填
private String ram; // 必填
private int usbCount = 0; // 选填,给默认值
private String keyboard; // 选填
private String display; // 选填
// 构造函数仅包含必填参数
public Builder(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
// 链式调用方法:设置完属性后返回 this
public Builder setUsbCount(int usbCount) {
this.usbCount = usbCount;
return this;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
public Builder setDisplay(String display) {
this.display = display;
return this;
}
// 核心方法:构建对象
public Computer build() {
return new Computer(this);
}
}
}2.2 客户端调用
代码变得非常清晰,像读文章一样。
public class Client {
public static void main(String[] args) {
Computer pc = new Computer.Builder("Intel i9", "32GB")
.setUsbCount(4)
.setKeyboard("Cherry MX")
.setDisplay("4K Monitor")
.build();
System.out.println(pc);
}
}3. 现代 Java 开发:Lombok 的 @Builder
在实际工作中,我们很少手写 Builder 模式,因为代码太冗余了。Lombok 插件通过注解帮我们生成所有代码。
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class User {
private String name;
private int age;
private String email;
}
// 客户端调用
public class Client {
public static void main(String[] args) {
User user = User.builder()
.name("Zhang San")
.age(25)
.email("zhangsan@example.com")
.build();
System.out.println(user);
}
}注意:Lombok 默认生成的 Builder 不会强制区分“必填”和“选填”参数。如果需要强制必填,还是需要手动写一点代码或者配合 @NonNull 注解。
4. 源码中的建造者模式
JDK 和常用框架中大量使用了 Builder 模式:
java.lang.StringBuilder: 这是最著名的 Builder。javaStringBuilder sb = new StringBuilder(); sb.append("Hello").append(" ").append("World"); // 链式调用 String result = sb.toString(); // build()虽然它没有叫
Builder,但append()方法返回this,最后toString()返回结果,完全符合 Builder 模式的思想。java.util.stream.Stream.Builder: 用于手动构建流。javaStream<String> stream = Stream.<String>builder() .add("a") .add("b") .add("c") .build();Spring 的
BeanDefinitionBuilder: 用于编程式地定义 Spring Bean。javaBeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyService.class); builder.addPropertyReference("dao", "myDao"); BeanDefinition beanDef = builder.getBeanDefinition();
5. 优缺点与适用场景总结
优点
- 链式调用,代码可读性高:不再是一堆看不懂的参数列表。
- 参数校验:可以在
build()方法中集中进行参数校验,确保构建出的对象是合法的。 - 不可变性:通常
Computer类的属性都是final的(虽然示例中没加),Builder 模式非常适合构建不可变对象 (Immutable Object)。
缺点
- 代码冗余:为了创建一个对象,必须多创建一个 Builder 对象。对于只有两三个参数的简单类,没必要用 Builder。
- 属性重复:Builder 类必须复制目标类的所有属性。
适用场景
- 构造函数参数超过 4 个,且很多参数是可选的。
- 创建的对象是复杂的,需要分步骤构建。
- 需要创建不可变对象。
6. 结语
建造者模式是解决“长参数列表”的良药。在 Effective Java 中,Josh Bloch 也极力推荐:当构造方法参数过多时,考虑用 Builder 模式。配合 Lombok,它几乎成为了现代 Java Bean 的标配。
7. 实验实操
在 design-patterns-web 项目中,你可以体验建造者模式构建复杂对象的过程——制作汉堡。 点击“Beef Patty”、“Cheese”、“Lettuce”等按钮,就像调用 Builder 的各个步骤方法(如 .addPatty(), .addCheese())。右侧的汉堡会实时根据你的选择一层层堆叠起来。最后点击“Complete Order”,Builder 会自动检查并加上顶层的面包(Top Bun),完成最终产品的构建。这展示了如何分步骤构建一个复杂的对象。 截图建议:截取构建过程中包含多层食材的汉堡,以及点击完成后的最终形态。