作者:明明如月学长, CSDN 博客专家,蚂蚁集团高级 Java 工程师,《性能优化方法论》作者、《解锁大厂思维:剖析《阿里巴巴Java开发手册》》、《再学经典:《EffectiveJava》独家解析》专栏作者。
热门文章推荐:
一、背景
在日常开发中,发现有些同学喜欢用 lombok 的 @Builder
注解,主要原因是喜欢使用链式编程。
但如果一个方法里面多个对象都使用 builder 模式,每个对象构建都放在一行使用,代码过长;如果每个属性设置都滑换行,占的行数就很多,导致可读性降低。
// 都放在一行,代码过宽
Person person = new Person.Builder().name("bob").age(23).gender("male").height(180).weight(75).build();
// 每个方法换行,占的行数过多
Person person = new Person.Builder()
.name("bob")
.age(23)
.gender("male")
.height(180)
.weight(75)
.build();
Builder 设计模式除了链式编程以外,还有啥好处吗?还有哪些副作用?使用的正确方式是怎样的?
二、Builder 模式
2.1 概念
builder 设计模式是一种将一个复杂对象的构建与其表示分离的方法,使得同样的构建过程可以创建不同的表示。
这种模式可以解决当一个类的构造函数参数过多,而且有些是可选的时候,使用构造器或者JavaBean模式会带来的问题,比如代码量大,灵活性低,对象状态不一致等。
Builder 设计模式的实现方法是在目标类中创建一个静态内部类 Builder
,然后将目标类中的参数都复制到Builder
类中。在目标类中创建一个私有的构造函数,参数为 Builder
类型。在Builder
类中提供设置各个参数的方法,并返回当前对象。最后在 Builder
类中提供一个 build
方法,用来创建目标类的实例,并将各个参数赋值给目标类。
2.2 Builder 模式的优缺点
Builder 设计模式的优点有:
- 可以将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
- 可以将类的构造函数中的可选参数分离出来,使用setter的方式进行初始化,非常的灵活。
- 可以使用链式调用,属性连续设置,看起来简洁,易于理解。
- 可以隔离复杂对象的创建和使用,隐藏了产品的内部结构和实现细节。
- 可以对产品进行更精细的控制,只需调用需要的构造步骤即可。
Builder 设计模式的缺点有:
- 产品组成部分必须相同,限制了使用范围。
- 如果产品内部变化复杂,会增加更多的具体建造者类,增加了系统的复杂度和运行成本。
- 需要创建额外的Builder接口和具体建造者类,增加了代码量。
- 与抽象工厂模式相比,不能很好地处理产品族的问题。
2.3 代码示例
一个简单的例子是用 builder 设计模式来创建一个 Computer
类的对象。Computer
类有一些必需的属性,如cpu
和 ram
,以及一些可选的属性,如 usbCount
和keyboard
。
使用 Builder 设计模式,我们可以在 Computer
类中创建一个静态内部类 Builder
,然后在 Builder
类中提供设置各个属性的方法,并返回当前对象。最后在 Builder
类中提供一个 build
方法,用来创建Computer
类的实例,并将各个属性赋值给 Computer
类。
代码示例如下:
public class Computer {
//必需的属性
private String cpu;
private String ram;
//可选的属性
private int usbCount;
private String keyboard;
//私有的构造函数,参数为Builder类型
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.usbCount = builder.usbCount;
this.keyboard = builder.keyboard;
}
//静态内部类Builder
public static class Builder {
//必需的属性
private String cpu;
private String ram;
//可选的属性
private int usbCount;
private String keyboard;
//设置必需的属性的构造函数
public Builder(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
//设置可选的属性的方法,并返回当前对象
public Builder setUsbCount(int usbCount) {
this.usbCount = usbCount;
return this;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
//创建Computer类的实例,并将各个属性赋值给Computer类
public Computer build() {
return new Computer(this);
}
}
}
使用这种方式,我们可以灵活地创建不同配置的 Computer
对象,例如:
//创建一个只有cpu和ram的Computer对象
Computer computer1 = new Computer.Builder("Intel Core i7", "16GB").build();
//创建一个有cpu、ram、usbCount和keyboard的Computer对象
Computer computer2 = new Computer.Builder("AMD Ryzen 9", "32GB")
.setUsbCount(4)
.setKeyboard("Logitech")
.build();
三、lombok 的 @Builder
注解
3.1 基本认识
lombok 的 @Builder
注解是一种实现 builder 设计模式的方式。
lombok 的 @Builder
注解可以生成一个构造器类,通过这个类你可以使用链式调用的方式来初始化你的对象。例如:
@Data
@Builder
public class User {
private Integer id;
private String name;
private String address;
}
User 编译后的代码:
// 使用 @Builder 生成的代码
public class User {
//三个属性
private Integer id;
private String name;
private String address;
//无参构造函数
public User() {
}
//全参构造函数
public User(Integer id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
// 省略其他方法
//builder()方法,创建构建器类的新实例
public static UserBuilder builder() {
return new UserBuilder();
}
//内部静态类UserBuilder,用于构建User对象
public static class UserBuilder {
//三个属性
private Integer id;
private String name;
private String address;
//包私有的无参构造函数
UserBuilder() {
}
//id属性的setter方法,返回构建器本身
public UserBuilder id(Integer id) {
this.id = id;
return this;
}
//name属性的setter方法,返回构建器本身
public UserBuilder name(String name) {
this.name = name;
return this;
}
//address属性的setter方法,返回构建器本身
public UserBuilder address(String address) {
this.address = address;
return this;
}
//build()方法,调用User类的全参构造函数,并返回User对象
public User build() {
return new User(id, name, address);
}
}
用法
User user = User.builder()
.id(1)
.name("张三")
.address("北京")
.build();
lombok 的 @Builder
注解可以让你省去写构造器类和各个属性的 setter 方法的麻烦。
3.2 副作用和注意事项
(1)如果你在类上使用了 @Builder
注解,那么你需要手动添加一个无参构造函数,否则有些序列化框架需要通过 newInstance 构造对象时会报错。
(2)如果你在类上使用了 @Builder
注解,那么你不能再在构造函数或方法上使用 @Builder
注解,否则会导致重复生成构造器类。
(3)如果你想给某个属性设置一个默认值,那么你需要在属性上使用 @Builder.Default
注解,否则默认值会被忽略。
(4)如果你想让子类继承父类的属性,那么你需要在子类的全参构造函数上使用 @Builder
注解,并且在父类上使用 @AllArgsConstructor
注解,否则子类的构造器类不会包含父类的属性。
四、总结
日常开发中有些同学喜欢用 @Builder
,我们在使用的时候不仅要享受它链式编程的好处,还要特别注意它的副作用。
Builder 设计模式的好处不仅是链式编程,更重要的是,可以通过 Builder 模式的构造方法来控制必传参数,还可以在设置参数方法或者在 build
方法中进行必传参数和参数合法性校验等。
当参数较少时,直接使用构造方法可能比 Builder 模式更简洁。
创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。