为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

MYYA 2023-03-02 15:39:53
想象一下,我有一个类封装了另一个类:@Builderpublic 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();    }  } }@Builderpublic 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;添加构建器方法以直接构建内部类也是好的编码习惯吗?或者它是否违反了某些设计原则,因为它可能耦合得太紧了?我已经遇到的一个问题是在对内部类进行重构时,我还必须对父类应用大部分相同的重构。
查看完整描述

1 回答

?
慕斯王

TA贡献1864条经验 获得超2个赞

在我看来,从风格/设计的角度来看,您的方法没有根本性的错误。但是,正如用户 JB Nizet 在评论中所解释的那样,存在两个主要问题:

  1. 您会遇到维护问题,因为您必须复制每个外部构建器方法。(Lombok@Delegate在这里帮不了你,因为它对 Lombok 本身生成的类不起作用。)

  2. 您的构建器的用户可以同时调用dimensions(Dimensions)委托方法和委托方法,这非常令人困惑。

从用户的角度来看,我希望这样使用构建器:

Dragon dragon = Dragon.builder()

    .dimensions()

        .height(12.0)

        .length(25.0)

        .back()

    .name("Smaug")

    .build();

这是实现它的方法(使用 Lombok 1.18.8):


@Builder

public class Dragon {

    private Dimensions dimensions;

    private String name;


    public static class DragonBuilder {


        private Dimensions.DimensionsBuilder innerBuilder = 

                new Dimensions.DimensionsBuilder(this);


        // If a method of the same name exists, Lombok does not generate

        // another one even if the parameters differ.

        // In this way, users cannot set their own dimensions object.

        public Dimensions.DimensionsBuilder dimensions() {

            return innerBuilder;

        }


        // Customize build() so that your innerBuilder is used to create 

        // the Dimensions instance.

        public Dragon build() {

            return new Dragon(innerBuilder.build(), name);

        }

    }

}

的构建器Dimensions持有对容器的引用DragonBuilder:


// Don't let Lombok create a builder() method, so users cannot 

// instantiate builders on their own.

@Builder(builderMethodName = "")

public class Dimensions {

    private double height;

    private double length;


    public static class DimensionsBuilder {

        private Dragon.DragonBuilder parentBuilder;


        // The only constructor takes a reference to the containing builder.

        DimensionsBuilder(Dragon.DragonBuilder parentBuilder) {

            this.parentBuilder = parentBuilder;

        }


        // Provide a method that returns the containing builder.

        public Dragon.DragonBuilder back() {

            return parentBuilder;

        }


        // The build() method should not be called directly, so 

        // we make it package-private.

        Dimensions build() {

            return new Dimensions(height, length);

        }

    }

}

这种方法可以扩展,因为 Lombok 会自动在构建器中生成所有必要的剩余 setter 方法。此外,由于用户提供了自己的Dimensions实例,因此可能不会出现意外。(你可以允许这样做,但我强烈建议对潜在的冲突进行运行时检查,例如检查是否已调用这两种方法。)


缺点是它Dimensions.builder()不再可用,因此不能直接使用或在其他具有Dimensions字段的类的构建器中使用。但是,也有一个解决方案:使用@SuperBuilder Dimensions并定义一个class NestedDimensionsBuilder extends Dimensions.DimensionsBuilder<Dimensions, NestedDimensionsBuilder>within DragonBuilder。


查看完整回答
反对 回复 2023-03-02
  • 1 回答
  • 0 关注
  • 102 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信