使用Lombok @Builder的方法生成器

2,015 阅读3分钟

 在本教程中,我们将探索使用Lombok的@Builder注释生成方法构建器的各种可能性,以提高可用性。

概观

在本教程中,我们将探索用Lombok生成方法构建器的可能性@Builder注释。目的是通过提供一种灵活的方式来调用一个给定的方法来提高可用性,即使它有很多参数。

@基于简单方法的构建器

如何为方法提供灵活的用法是一个普遍的话题可能接受多个输入。看一下下面的例子:

void method(@NotNull String firstParam, @NotNull String secondParam, 
            String thirdParam, String fourthParam, 
            Long fifthParam, @NotNull Object sixthParam) {
    ...            
}

如果未标记为not null的参数是可选的,则该方法可能会接受以下所有调用:

method("A", "B", null, null, null, new Object());
method("A", "B", "C", null, 2L, "D");
method("A", "B", null, null, 3L, this);
...

这个例子已经显示了一些问题点,例如:

  • 调用者应该知道哪个参数是哪个参数(例如,为了改变第一次调用以提供Long打电话的人也一定知道Long预计是第五个参数)。
  • 输入必须按照给定的顺序设置。
  • 输入参数的名称是不透明的。

同时,从提供者的角度来看,为方法提供更少的参数意味着方法名的大量重载,例如:

void method(@NotNull String firstParam, @NotNull String secondParam, @NotNull Object sixthParam);
void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, @NotNull Object sixthParam);
void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, @NotNull Object sixthParam);
void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, Long fifthParam, @NotNull Object sixthParam);
...

为了获得更好的可用性并避免样板代码,可以引入方法构建器。Lombok项目已经提供了一个注释,以便简化构建器的使用。上面的示例方法可以用以下方式进行注释:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call")
void method(@NotNull String firstParam, @NotNull String secondParam, 
            String thirdParam, String fourthParam, 
            Long fifthParam, @NotNull Object sixthParam) {
    ...            
}

因此,调用该方法将类似于:

methodBuilder()
        .firstParam("A")
        .secondParam("B")
        .sixthParam(new Object())
        .call();

methodBuilder()
        .firstParam("A")
        .secondParam("B")
        .thirdParam("C")
        .fifthParam(2L)
        .sixthParam("D")
        .call();

methodBuilder()
        .firstParam("A")
        .secondParam("B")
        .fifthParam(3L)
        .sixthParam(this)
        .call();

这样,方法调用就更容易理解,以后也更容易修改。一些评论:

  • 默认情况下,静态方法上的构建器方法(获取构建器实例的方法)本身就是一个静态方法。
  • 默认情况下call()方法将具有与原始方法相同的抛出签名。

默认值

在许多情况下,为输入参数定义默认值非常有用。与其他一些语言不同,Java没有支持这种需求的语言元素。因此,在大多数情况下,这是通过方法重载实现的,其结构如下:

method() { method("Hello"); }
method(String a) { method(a, "builder"); }
method(String a, String b) { method(a, b, "world!"); }
method(String a, String b, String c) { ... acutal logic here ... }

使用Lombok builders时,将在目标类中生成一个构建器类。该生成器类:

  • 与方法具有相同数量的属性和参数。
  • 为其参数设置了。

也可以手动定义类,这提供了为参数定义默认值的可能性。这样,上面的方法看起来就像这样:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
method(String a, String b, String c) {
  ... acutal logic here ...
}

private class MethodBuilder {
    private String a = "Hello";
    private String b = "builder";
    private String c = "world!";
}

通过这种添加,如果调用方没有指定参数,将使用builder类中定义的默认值。

注意:在这种情况下,我们不必在类中声明方法的所有输入参数。如果该方法的输入参数不在类中,Lombok将相应地生成一个附加属性。

类型化方法

通常需要通过一个输入来定义给定方法的返回类型,例如:

public <T> T read(byte[] content, Class<T> type) {...}

在这种情况下,builder类也将是一个类型化类,但是builder方法将创建一个没有有界类型的实例。看一下下面的例子:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
public <T> T read(byte[] content, Class<T> type) {...}

在这种情况下methodBuilder方法将创建一个MethodBuilder没有有界类型参数。这导致以下代码无法编译(这是Class<T>,由提供Class<String>):

methodBuilder()
    .content(new byte[]{})
    .type(String.class)
    .call();

这可以通过将type并将其用作:

methodBuilder()
    .content(new byte[]{})
    .type((Class)String.class)
    .call();

它会编译,但还有另一个方面需要提及:在这种情况下,call方法的返回类型不会是String,但仍然是未绑定的t。因此,客户端必须像这样转换返回类型:

String result = (String)methodBuilder()
    .content(new byte[]{})
    .type((Class)String.class)
    .call();

这种解决方案是可行的,但是它也要求调用者同时转换输入和结果。因为最初的动机是提供一种调用方友好的方法来调用这些方法,所以建议考虑以下两个选项之一。

重写生成器方法

如上所述,问题的根源在于builder方法创建了builder类的一个实例,而没有指定类型参数。仍然可以在类中定义builder方法,并创建具有所需类型的builder类的实例:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
public <T extends Collection> T read(final byte[] content, final Class<T> type) {...}

public <T extends Collection> MethodBuilder<T> methodBuilder(final Class<T> type) {
    return new MethodBuilder<T>().type(type);
}

public class MethodBuilder<T extends Collection> {
    private Class<T> type;
    public MethodBuilder<T> type(Class<T> type) { this.type = type; return this; }
    public T call() { return read(content, type); }
}

在这种情况下,调用者不必随时强制转换,调用如下所示:

List result = methodBuilder(List.class)
    .content(new byte[]{})
    .call();

在设定器中铸造

也可以在类型参数的setter中强制转换生成器实例:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
public <T extends Collection> T read(final byte[] content, final Class<T> type) {...}

public class MethodBuilder<T extends Collection> {
    private Class<T> type;
    public <L extends Collection> MethodBuilder<L> type(final Class<L> type) { 
        this.type = (Class)type; 
        return (MethodBuilder<L>) this;
    }
    public T call() { return read(content, type); }
}

使用这种方式,不需要手动定义builder方法,从调用者的角度来看,类型参数就像任何其他参数一样被传递。

结论

在方法上使用@Builder可以带来以下好处:

  • 呼叫者方面的更多灵活性
  • 没有方法重载的默认输入值
  • 提高了方法调用的可读性
  • 允许通过同一个生成器实例进行类似的调用

同时,还值得一提的是,在某些情况下,使用方法构建器会给提供者带来不必要的复杂性

感谢阅读,点赞收藏+关注

6bf40069c67a99cd90d3690b5feabf8bd42b9db3.jpg