构建卓越:深入解析Java中的Builder模式与灵活对象创建
概述:
工作中,new 一个对象,如果想要充实这个对象,要么手动set,要么使用构造函数,如果不同的地方使用不同的构造函数,则需要很多构造函数进行搭配或者疯狂的set。当然,也有同学说可以使用lombok的Builder 进行构建但是需要依赖lombok并且该组件有一定bug,很多项目不敢使用。故此针对两者给出对应优缺点介绍:
Lombok @Builder
:
- 优点: - 减少样板代码:自动生成构建器代码,减少了手动编写的需要。 - 易于使用:添加一个注解就可以使用。 - 链式调用:生成的代码支持链式调用,使代码更简洁。 - 一致性:生成的代码风格一致,减少了人为错误。
- 缺点: - 需要插件:IDE 需要安装 Lombok 插件才能正确识别 Lombok 生成的代码。 - 编译时依赖:Lombok 是一个编译时的依赖,它在编译时期修改字节码。 - 难以调试:由于代码是自动生成的,可能会使得调试过程变得复杂。 - 可定制性差:相比手动实现的构建器,Lombok 提供的定制性较差。
手动实现的 Builder
:
- 优点: - 高度可定制:可以根据需要添加自定义逻辑和验证。 - 无需额外插件:不依赖 IDE 插件或特定的构建工具。 - 透明性:所有代码都是可见的,便于理解和调试。
- 缺点: - 更多的样板代码:需要手动编写构建器类,增加了代码量。 - 一致性依赖于开发者:代码风格和一致性依赖于开发者的实现。 - 维护成本:随着类的字段增加,需要手动更新构建器类。
在选择使用 Lombok 的 @Builder
或手动实现的 Builder
时,需要根据项目的需要和开发团队的偏好来决定。如果项目中大量使用构建器模式且希望减少样板代码,Lombok 可能是一个好选择。但如果需要更多的控制和定制,或者想避免额外的编译时依赖,手动实现的构建器可能更合适。
故此,针对以上,我这边实现一个手动Builder工具类:
Builder工具类:
import org.apache.logging.log4j.util.TriConsumer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* @Author derek_smart
* @Date 2024/6/3 16:01
* @Description
* <p> Builder
*/
public class Builder<T> {
private final Supplier<T> instanceSupplier;
private final List<Consumer<T>> setters;
private Builder(Supplier<T> instanceSupplier) {
this.instanceSupplier = instanceSupplier;
this.setters = new ArrayList<>();
}
public static <E> Builder<E> of(Supplier<E> instanceSupplier) {
return new Builder<>(instanceSupplier);
}
public <P> Builder<T> with(BiConsumer<T, P> consumer, P p) {
this.setters.add(instance -> consumer.accept(instance, p));
return this;
}
public <P1, P2> Builder<T> with(TriConsumer<T, P1, P2> consumer, P1 p1, P2 p2) {
this.setters.add(instance -> consumer.accept(instance, p1, p2));
return this;
}
public T build() {
T t = this.instanceSupplier.get();
this.setters.forEach(c -> c.accept(t));
return t;
}
}
类和方法介绍
-
字段:
instanceSupplier
: 一个Supplier<T>
类型的字段,用于提供一个新的对象实例。setters
: 一个Consumer<T>
类型的列表,存储一系列的设置操作(setter)。
-
构造函数:
private Builder(Supplier<T> instanceSupplier)
: 私有构造函数,接收一个Supplier<T>
实例,该实例用于在build
方法中提供新对象的实例。
-
静态工厂方法:
public static <E> Builder<E> of(Supplier<E> instanceSupplier)
: 一个泛型静态方法,用于创建Builder
实例。它接收一个Supplier<E>
参数,用于后续提供新对象的实例。
-
with 方法:
public <P> Builder<T> with(BiConsumer<T, P> consumer, P p)
: 接收一个BiConsumer<T, P>
和一个参数p
,将设置操作(setter)添加到setters
列表中。BiConsumer
是一个函数式接口,接收两个参数,用于设置对象的一个属性。public <P1, P2> Builder<T> with(TriConsumer<T, P1, P2> consumer, P1 p1, P2 p2)
: 接收一个自定义的TriConsumer<T, P1, P2>
接口和两个参数,将设置操作(setter)添加到setters
列表中。TriConsumer
是一个假设存在的自定义函数式接口,它接收三个参数。
-
build 方法:
public T build()
: 创建对象实例,并遍历setters
列表,将每个设置操作(setter)应用到对象实例上,然后返回这个配置好的对象实例。
使用场景
Builder<T>
类的使用场景通常涉及以下情况:
-
构造复杂对象:当你需要构造一个属性较多的对象,且这些属性不都是必需设置的时候,使用
Builder
类可以使构造过程更加清晰和灵活。 -
不可变对象:对于不可变对象,一旦创建就不能更改其状态,使用构建器模式可以一次性设置所有属性,然后创建对象。
-
参数过多的构造函数:如果一个类有多个构造函数,或者构造函数的参数列表很长,使用
Builder
类可以简化构造过程,提高代码的可读性。 -
链式调用:
Builder
类允许链式调用with
方法来连续设置属性,这使得代码更加简洁。
使用示例:
public class Person {
private String name;
private int age;
private String address;
//... get set 方法省略
}
Person person = Builder.of(Person::new)
.with(Person::setName, "Derek")
.with(Person::setAge, 25)
.with(Person::setAddress, "上海市徐汇区钦州路100号")
.build();
总结:
Builder<T>
类是一个通用的构建器(Builder)类,用于灵活地构造和配置任意类型 T
的对象实例。它采用了一种流式接口(Fluent Interface)和链式调用的方法来简化对象的创建和设置过程。
Builder 类是一种实现构建器模式的工具,旨在提供一种灵活、清晰且可维护的方法来构造复杂对象。通过链式调用和分步设置对象属性,Builder 类能够简化对象的创建过程,特别适用于具有多个属性的对象。Lombok 的 @Builder
注解自动化了构建器模式的实现,减少了样板代码,提高了开发效率,但牺牲了一定的可定制性和透明度。相比之下,手动实现的 Builder 类提供了更高的定制性和控制力,允许开发者在构建对象时加入自定义逻辑,但需要编写更多的代码并手动维护。选择哪种实现方式取决于项目需求、团队偏好以及对于代码透明度和可维护性的考量。
转载自:https://juejin.cn/post/7376532114105712691