builder(java:其实你并不会用Builder)

时间:2024-11-23 21:47:15 阅读:6

java:但是你并不会用Builder

天生器形式在Java使用步骤中十分盛行。但它常常被各位曲解和错误地使用,从而招致运转时错误。

让我们记取使用Builder的目标:仅在某些目标中设置必要的字段,并将其他字段设置为默许值。比如,假如我们正在准备设置目标,那么仅变动必要的参数并将其他参数设置为默许值会很便利。

而当Builder是反形式时

很多开发职员只选择了Builder形式的一局部:可以单独设置字段。第二局部:其他字段存在公道的默许值-通常会被忽略。

后果,很容易取得不完备的(局部初始化的)POJO。为了缓解此成绩,我们对build()办法举行了反省,最初约莫本人都没发觉啥成绩但就是出错。此时如今,主要的侵害以前形成:反省转移到了运转时间上。为确保统统正常,我们必要添加自用测试以掩盖创建POJO的代码中的一切实行途径。

怎样修复POJO的天生器?

起首,让我们界说目标。这里的目标是将反省前往到编译时。假如未构建完备POJO的代码无法经过编译,则将不必要自用测试,也无需在build()办法中实行反省。但最紧张的是,平常我们事情起来就更轻松。有更多时间摸鱼了。

那么,怎样做到这一点呢?最分明的办法是使用Fluent API形式。Fluent API有两个局部(特地说一句,就像Builder一样):提供一种便利的办法来调用链中的办法(这两个局部Fluent API和Builder都相反),并将链中的每个后续调用都限定为仅允许的办法集。

第二局部是我们所要说的局部。经过限定在构建POJO的每个步调中可以调用的办法集,我们可以欺压实行特定的调用序列,并build()仅在设置了一切字段时才启用对办法的调用。如此,我们将一切反省移回编译时间。作为便利的反作用,还确保构建特定POJO的一切地点看起来都相反。如此,发觉错误转达的参数或比力代码修订之间的变动将愈加容易。

为了区分传统的Builder和带有Fluent API的Builder,我将后者称为Fluent Builder

假定我们要为如下所示的简便bean创建Fluent Builder:

public class SimpleBean { private final int index; private final String name; public SimpleBean(final int index, final String name) { this.index = index; this.name = name; } public int index() { return index; } public String name() { return name; } }

在此示例中,我使用了Java 纪录获取器的称呼商定。在Java 14中,此类可以声明为纪录,因此必要的样板代码将大大变小。

让我们添加一个构建器。第一步很传统:

... public static SimpleBeanBuilder builder() { return new SimpleBeanBuilder(); } ...

让我们起首完成一个传统的天生器,如此会愈加清晰Fluent Builder代码是怎样派生的。传统的Builder类如下所示:

... private static class SimpleBeanBuilder { private int index; private String name; public SimpleBeanBuilder setIndex(final int index) { this.index = index; return this; } public SimpleBeanBuilder setName(final String name) { this.name = name; return this; } public SimpleBean build() { return new SimpleBean(index, name); } } ...

一个紧张的察看:每个setter前往此值,这又允许此调用的用户调用builder中可用的每个办法。这是成绩的本源,由于build()在设置一切必要字段之前,允许用户过早调用该办法。

为了制造Fluent Builder,我们必要将约莫的选择限定为仅允许的选择,因此必需准确使用Builder。由于我们正在思索必要设置一切字段的情况,因此在每个构建步调中,仅有一种办法可用。为此,我们可以前往自用接口,而不是this让Builder完成一切这些接口:

... public static SimpleBeanBuilder0 builder() { return new SimpleBeanBuilder(); } ... private static class SimpleBeanBuilder implements SimpleBeanBuilder0, SimpleBeanBuilder1, SimpleBeanBuilder2 { private int index; private String name; public SimpleBeanBuilder1 setIndex(final int index) { this.index = index; return this; } public SimpleBeanBuilder2 setName(final String name) { this.name = name; return this; } public SimpleBean build() { return new SimpleBean(index, name); } public interface SimpleBeanBuilder0 { SimpleBeanBuilder1 setIndex(final int index); } public interface SimpleBeanBuilder1 { SimpleBeanBuilder2 setName(final String name); } public interface SimpleBeanBuilder2 { SimpleBean build(); } }

这模板有点丑,换个办法。

第一步是中止完成接口,而是前往完成这些接口的匿名类:

... public static SimpleBeanBuilder builder() { return new SimpleBeanBuilder(); } ... private static class SimpleBeanBuilder { public SimpleBeanBuilder1 setIndex(int index) { return new SimpleBeanBuilder1() { @Override public SimpleBeanBuilder2 setName(final String name) { return new SimpleBeanBuilder2() { @Override public SimpleBean build() { return new SimpleBean(index, name); } }; } }; } public interface SimpleBeanBuilder1 { SimpleBeanBuilder2 setName(final String name); } public interface SimpleBeanBuilder2 { SimpleBean build(); } }

如此很多了。我们可以再次宁静地SimpleBeanBuilder从该builder()办法前往,由于此类仅公开一个办法,并且不允许用户过早构建实例。但更紧张的是,我们可以省略构建器中的整个设置器和可变字段样板,从而大大变小了代码量。这是约莫的,由于我们在可见和可拜候一切设置器参数的范围内创建了匿名类。

就代码总数而言,天生的代码与原始Builder完成相当。

但这还不是全部。由于一切匿名类实践上都是仅包含一种办法的接口的完成,因此我们可以用lambda交换匿名类:

private static class SimpleBeanBuilder { public SimpleBeanBuilder1 setIndex(int index) { return name -> () -> new SimpleBean(index, name); } public interface SimpleBeanBuilder1 { SimpleBeanBuilder2 setName(final String name); } public interface SimpleBeanBuilder2 { SimpleBean build(); } }

注意,剩余的SimpleBeanBuilder类与其他构建器接口十分相似,因此也可以将其交换为lambda:

public static SimpleBeanBuilder builder() { return index -> name -> () -> new SimpleBean(index, name); } public interface SimpleBeanBuilder { SimpleBeanBuilder1 setIndex(int index); } public interface SimpleBeanBuilder1 { SimpleBeanBuilder2 setName(final String name); } public interface SimpleBeanBuilder2 { SimpleBean build(); }

最初:

  • 在SimpleBeanBuilder接口内挪动接口,并对接口举行一些重定名。由于这些接口很少会显如今用户代码中,因此我们可以为其使用一些标准化的定名办法,并简化代码的主动天生。
  • 重定名设置器,由于不必要Java Bean定名商定,由于这里没有获取器。
  • 版本()办法是没有必要了。在最初的完成中,它表现以前完成POJO的组装,但这不再是必需的,由于一旦设置了最初一个字段,便拥有了构建POJO实例的一切必要细节。

底下是SimpleBean使用一切这些变动之后的完备代码:

public class SimpleBean { private final int index; private final String name; private SimpleBean(final int index, final String name) { this.index = index; this.name = name; } public int index() { return index; } public String name() { return name; } public static Builder builder() { return index -> name -> new SimpleBean(index, name); } public interface Builder { Stage1 index(int index); interface Stage1 { SimpleBean name(final String name); } } }

实践上实行少数利用的代码很少,大大多完成是一堆接口声明。添加,变动或删除字段十分简便,由于触及的代码很少。

关于尚未习气使用深层嵌套lambda的用户,此代码乍一看约莫会比力困难,但这是履历成绩。别的,无需手动编写此类代码,由于我们可以将此职责卸载到IDE(就像我们使用传统构建器一样)。

使用上述办法,我们可以用Fluent Builders代替传统的Builders,并经过Fluent API形式宁静性取得Builder的简便利用。

版权声明:本文来自互联网整理发布,如有侵权,联系删除

原文链接:https://www.yigezhs.comhttps://www.yigezhs.com/wangluozixun/55899.html


Copyright © 2021-2022 All Rights Reserved 备案编号:闽ICP备2023009674号 网站地图 联系:dhh0407@outlook.com