建造者模式:一步一步搭建复杂对象
在软件开发中,我们经常会遇到需要创建复杂对象的场景。这些对象可能包含多个属性,其中一些是必需的,另一些是可选的,而且构建过程可能涉及多个步骤。传统的构造方法在面对这种情况时往往显得力不从心,要么导致构造函数参数列表过长,要么使得客户端代码难以理解和维护。建造者模式正是为了解决这些问题而生的。
为什么需要建造者模式?
传统构造方式的痛点
假设我们需要构建一个User
类,它包含以下属性:
public class User {
private final String firstName; // 必需
private final String lastName; // 必需
private final int age; // 可选
private final String phone; // 可选
private final String address; // 可选
private final String email; // 可选
// 构造函数...
}
使用传统的构造方法,我们会面临以下问题:
1. 伸缩构造函数模式(Telescoping Constructor Pattern)
public User(String firstName, String lastName) {
this(firstName, lastName, 0);
}
public User(String firstName, String lastName, int age) {
this(firstName, lastName, age, "");
}
public User(String firstName, String lastName, int age, String phone) {
this(firstName, lastName, age, phone, "");
}
// 更多构造函数...
这种方式虽然可行,但当参数增多时,构造函数数量会呈指数级增长,而且客户端代码很难弄清楚每个参数的含义。
2. JavaBean模式
public class User {
private String firstName;
private String lastName;
private int age;
private String phone;
private String address;
private String email;
// 无参构造函数 + setter方法
public User() {}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
// 其他setter方法...
}
这种方式虽然简单,但失去了不可变性(对象状态可能在构建后被修改),而且构建过程被分成了多个步骤,无法保证对象在构建过程中的一致性。
建造者模式的核心思想
建造者模式将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。它通过一个指导者(Director)来指导构建过程,通过具体的建造者(Concrete Builder)来实现不同表示的构建。
模式结构
- Product(产品):要创建的复杂对象
- Builder(建造者):抽象接口,定义创建产品各个部件的操作
- ConcreteBuilder(具体建造者):实现Builder接口,构造和装配各个部件
- Director(指导者):构造一个使用Builder接口的对象
建造者模式的实现
基础实现
让我们用建造者模式重新设计User
类:
public class User {
private final String firstName;
private final String lastName;
private final int age;
private final String phone;
private final String address;
private final String email;
// 私有构造函数,只能通过Builder创建
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
this.email = builder.email;
}
// 静态内部类 Builder
public static class UserBuilder {
// 必需参数
private final String firstName;
private final String lastName;
// 可选参数 - 初始化为默认值
private int age = 0;
private String phone = "";
private String address = "";
private String email = "";
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public UserBuilder email(String email) {
this.email = email;
return this;
}
// 构建方法
public User build() {
return new User(this);
}
}
// getter方法
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public int getAge() { return age; }
public String getPhone() { return phone; }
public String getAddress() { return address; }
public String getEmail() { return email; }
@Override
public String toString() {
return "User{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
", phone='" + phone + '\'' +
", address='" + address + '\'' +
", email='" + email + '\'' +
'}';
}
}
客户端使用方式
public class BuilderPatternDemo {
public static void main(String[] args) {
// 创建User对象 - 只设置必需参数
User user1 = new User.UserBuilder("张", "三").build();
// 创建User对象 - 设置部分可选参数
User user2 = new User.UserBuilder("李", "四")
.age(25)
.phone("13800138000")
.build();
// 创建User对象 - 设置所有参数
User user3 = new User.UserBuilder("王", "五")
.age(30)
.phone("13900139000")
.address("北京市朝阳区")
.email("wangwu@example.com")
.build();
System.out.println(user1);
System.out.println(user2);
System.out.println(user3);
}
}
带验证的建造者模式
在实际应用中,我们通常需要在构建过程中进行参数验证:
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age = 0;
private String phone = "";
private String address = "";
private String email = "";
public UserBuilder(String firstName, String lastName) {
if (firstName == null || firstName.trim().isEmpty()) {
throw new IllegalArgumentException("firstName不能为空");
}
if (lastName == null || lastName.trim().isEmpty()) {
throw new IllegalArgumentException("lastName不能为空");
}
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄必须在0-150之间");
}
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
if (phone != null && !phone.matches("\\d{11}")) {
throw new IllegalArgumentException("手机号格式不正确");
}
this.phone = phone;
return this;
}
public User build() {
// 构建前的最终验证
if (age < 18 && email == null) {
throw new IllegalStateException("未成年人必须提供邮箱");
}
return new User(this);
}
}
实际应用场景
1. 配置对象的构建
在框架开发中,建造者模式常用于构建配置对象:
// 数据库配置
DataSourceConfig config = new DataSourceConfig.Builder()
.url("jdbc:mysql://localhost:3306/test")
.username("root")
.password("password")
.maxPoolSize(20)
.minPoolSize(5)
.connectionTimeout(30000)
.build();
2. 复杂查询构建
在构建复杂查询时,建造者模式特别有用:
// SQL查询构建
Query query = new QueryBuilder()
.select("id", "name", "email")
.from("users")
.where("age > ?", 18)
.and("status = ?", "ACTIVE")
.orderBy("created_at", "DESC")
.limit(10)
.offset(0)
.build();
3. HTTP请求构建
// HTTP请求构建
HttpRequest request = new HttpRequestBuilder()
.url("https://api.example.com/users")
.method("POST")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token")
.body("{\"name\":\"张三\",\"age\":25}")
.timeout(5000)
.build();
在开源框架中的应用
Lombok的@Builder注解
Lombok提供了@Builder
注解,可以自动生成建造者模式的代码:
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class User {
private final String firstName;
private final String lastName;
private final int age;
private final String phone;
private final String address;
private final String email;
}
// 使用方式
User user = User.builder()
.firstName("张")
.lastName("三")
.age(25)
.phone("13800138000")
.build();
StringBuilder和StringBuffer
Java标准库中的StringBuilder
和StringBuffer
就是建造者模式的典型应用:
StringBuilder sb = new StringBuilder();
String result = sb.append("Hello")
.append(" ")
.append("World")
.append("!")
.toString();
建造者模式的变体
1. 流式接口(Fluent Interface)
通过方法链式调用,让代码更加流畅易读:
public class Pizza {
private String size;
private boolean cheese;
private boolean pepperoni;
private boolean bacon;
public static class Builder {
private String size;
private boolean cheese = false;
private boolean pepperoni = false;
private boolean bacon = false;
public Builder(String size) {
this.size = size;
}
public Builder addCheese() {
this.cheese = true;
return this;
}
public Builder addPepperoni() {
this.pepperoni = true;
return this;
}
public Builder addBacon() {
this.bacon = true;
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
private Pizza(Builder builder) {
this.size = builder.size;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
this.bacon = builder.bacon;
}
}
// 使用
Pizza pizza = new Pizza.Builder("Large")
.addCheese()
.addPepperoni()
.addBacon()
.build();
2. 带指导者的建造者模式
当构建过程特别复杂时,可以引入指导者角色:
// 指导者
public class UserDirector {
public User constructAdultUser(UserBuilder builder, String firstName, String lastName) {
return builder.firstName(firstName)
.lastName(lastName)
.age(18)
.email(firstName + "." + lastName + "@example.com")
.build();
}
public User constructSeniorUser(UserBuilder builder, String firstName, String lastName) {
return builder.firstName(firstName)
.lastName(lastName)
.age(65)
.phone("400-000-0000")
.address("养老院")
.build();
}
}
建造者模式的优缺点
优点
- 封装性好:建造者模式将复杂对象的构建过程封装起来,客户端不需要知道内部细节
- 易于扩展:当需要构建不同表示的对象时,只需要增加相应的具体建造者
- 便于控制构建过程:指导者类可以精细控制构建过程
- 提高代码可读性:方法链式调用让代码更加清晰易读
- 保证对象不可变性:构建完成后对象状态不可修改
缺点
- 代码复杂度增加:需要创建多个类,代码量增加
- 适用范围有限:主要用于创建复杂对象,简单对象使用建造者模式反而会增加复杂度
- 产品需要有共同点:如果产品之间差异很大,建造者模式就不太适用
总结
建造者模式是创建复杂对象的利器,它通过将构建过程与表示分离,提供了灵活、可读性强的对象创建方式。虽然会增加一些代码复杂度,但在面对包含多个可选参数的复杂对象时,这种投入是值得的。
在实际开发中,当遇到以下情况时,考虑使用建造者模式:
- 对象包含大量属性,其中很多是可选的
- 对象的构建过程复杂,涉及多个步骤
- 需要创建不同表示的对象,但构建过程相似
- 希望创建不可变对象
通过合理运用建造者模式,我们可以编写出更加健壮、可维护的代码,让复杂对象的创建变得简单而优雅。