使用 Builder 也可以构建封装对象

想象一下,我有一个类封装了另一个类:


@Builder

public class Dragon {


  private Dimensions dimensions;

  private String name;


  public static class ParentBuilder {

    DimensionsBuilder innerBuilder = Dimensions.builder();


    public DragonBuilder height(double height) {

      this.innerBuilder.height(height);

      return this;

    }


    public DragonBuilder length(double length) {

      this.innerBuilder.length(length);

      return this;

    }


    public Dragon build() {

      return Dragon.builder()

        .dimensions(this.innerBuilder.build())

        .name(this.name)

        .build();

    }

  } 

}


@Builder

public class Dimensions {

  private double height;

  private double length;

}

请记住,这是一个非常简化的示例,真正的代码(不幸的是,与龙无关)将很多属性委托给innerBuilder.


这样,我可以像这样实例化类:


Dragon dragon = Dragon.builder()

  .height(12.0)

  .length(25.0)

  .name("Smaug")

  .build();

而不是这样:


Dragon dragon = Dragon.builder()

  .dimensions(Dimensions.builder()

    .height(12.0)

    .length(25.0)

    .build())

  .name("Smaug")

  .build;

添加构建器方法以直接构建内部类也是好的编码习惯吗?或者它是否违反了某些设计原则,因为它可能耦合得太紧了?


我已经遇到的一个问题是在对内部类进行重构时,我还必须对父类应用大部分相同的重构。


MYYA
浏览 107回答 1
1回答

慕斯王

在我看来,从风格/设计的角度来看,您的方法没有根本性的错误。但是,正如用户 JB Nizet 在评论中所解释的那样,存在两个主要问题:您会遇到维护问题,因为您必须复制每个外部构建器方法。(Lombok@Delegate在这里帮不了你,因为它对 Lombok 本身生成的类不起作用。)您的构建器的用户可以同时调用dimensions(Dimensions)委托方法和委托方法,这非常令人困惑。从用户的角度来看,我希望这样使用构建器:Dragon dragon = Dragon.builder()&nbsp; &nbsp; .dimensions()&nbsp; &nbsp; &nbsp; &nbsp; .height(12.0)&nbsp; &nbsp; &nbsp; &nbsp; .length(25.0)&nbsp; &nbsp; &nbsp; &nbsp; .back()&nbsp; &nbsp; .name("Smaug")&nbsp; &nbsp; .build();这是实现它的方法(使用 Lombok 1.18.8):@Builderpublic class Dragon {&nbsp; &nbsp; private Dimensions dimensions;&nbsp; &nbsp; private String name;&nbsp; &nbsp; public static class DragonBuilder {&nbsp; &nbsp; &nbsp; &nbsp; private Dimensions.DimensionsBuilder innerBuilder =&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new Dimensions.DimensionsBuilder(this);&nbsp; &nbsp; &nbsp; &nbsp; // If a method of the same name exists, Lombok does not generate&nbsp; &nbsp; &nbsp; &nbsp; // another one even if the parameters differ.&nbsp; &nbsp; &nbsp; &nbsp; // In this way, users cannot set their own dimensions object.&nbsp; &nbsp; &nbsp; &nbsp; public Dimensions.DimensionsBuilder dimensions() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return innerBuilder;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; // Customize build() so that your innerBuilder is used to create&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; // the Dimensions instance.&nbsp; &nbsp; &nbsp; &nbsp; public Dragon build() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new Dragon(innerBuilder.build(), name);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}的构建器Dimensions持有对容器的引用DragonBuilder:// Don't let Lombok create a builder() method, so users cannot&nbsp;// instantiate builders on their own.@Builder(builderMethodName = "")public class Dimensions {&nbsp; &nbsp; private double height;&nbsp; &nbsp; private double length;&nbsp; &nbsp; public static class DimensionsBuilder {&nbsp; &nbsp; &nbsp; &nbsp; private Dragon.DragonBuilder parentBuilder;&nbsp; &nbsp; &nbsp; &nbsp; // The only constructor takes a reference to the containing builder.&nbsp; &nbsp; &nbsp; &nbsp; DimensionsBuilder(Dragon.DragonBuilder parentBuilder) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.parentBuilder = parentBuilder;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; // Provide a method that returns the containing builder.&nbsp; &nbsp; &nbsp; &nbsp; public Dragon.DragonBuilder back() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return parentBuilder;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; // The build() method should not be called directly, so&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; // we make it package-private.&nbsp; &nbsp; &nbsp; &nbsp; Dimensions build() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new Dimensions(height, length);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}这种方法可以扩展,因为 Lombok 会自动在构建器中生成所有必要的剩余 setter 方法。此外,由于用户提供了自己的Dimensions实例,因此可能不会出现意外。(你可以允许这样做,但我强烈建议对潜在的冲突进行运行时检查,例如检查是否已调用这两种方法。)缺点是它Dimensions.builder()不再可用,因此不能直接使用或在其他具有Dimensions字段的类的构建器中使用。但是,也有一个解决方案:使用@SuperBuilder Dimensions并定义一个class NestedDimensionsBuilder extends Dimensions.DimensionsBuilder<Dimensions, NestedDimensionsBuilder>within DragonBuilder。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java