Java 使用Builder同时构建封装对象

Java 使用Builder同时构建封装对象,java,design-patterns,encapsulation,builder,lombok,Java,Design Patterns,Encapsulation,Builder,Lombok,假设我有一个封装了另一个类的类: @Builder 公务舱龙{ 私人层面; 私有字符串名称; 公共静态类ParentBuilder{ DimensionsBuilder innerBuilder=Dimensions.builder(); 公共建筑高度(双高){ this.innerBuilder.height(高度); 归还这个; } 公共长度(双倍长度){ this.innerBuilder.length(长度); 归还这个; } 公龙大厦({ returndragon.builder()

假设我有一个封装了另一个类的类:

@Builder
公务舱龙{
私人层面;
私有字符串名称;
公共静态类ParentBuilder{
DimensionsBuilder innerBuilder=Dimensions.builder();
公共建筑高度(双高){
this.innerBuilder.height(高度);
归还这个;
}
公共长度(双倍长度){
this.innerBuilder.length(长度);
归还这个;
}
公龙大厦({
returndragon.builder()
.dimensions(此.innerBuilder.build())
.name(此.name)
.build();
}
} 
}
@建筑商
公共类维度{
私人双高;
私人双倍长度;
}
请记住,这是一个非常简化的示例,真正的代码(不幸的是,与龙无关)将大量属性委托给
innerBuilder

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

Dragon dragon = Dragon.builder()
    .dimensions()
        .height(12.0)
        .length(25.0)
        .back()
    .name("Smaug")
    .build();
Dragon=Dragon.builder()
.高度(12.0)
.长度(25.0)
.名称(“Smaug”)
.build();
而不是像这样:

Dragon dragon = Dragon.builder()
    .dimensions()
        .height(12.0)
        .length(25.0)
        .back()
    .name("Smaug")
    .build();
Dragon=Dragon.builder()
.dimensions(dimensions.builder()
.高度(12.0)
.长度(25.0)
.build())
.名称(“Smaug”)
.建造;
添加生成器方法来直接构建内部类是否也是一种良好的编码实践?或者它违反了一些设计原则,因为它可能是紧密耦合的


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

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

  • 您会遇到维护问题,因为您必须复制每个外部生成器方法。(Lombok的
    @Delegate
    在这里帮不了你,因为它不能处理Lombok本身生成的类。)
  • 构建器的用户可以调用
    dimensions(dimensions)
    和委托方法,这非常令人困惑
  • 从用户角度来看,我希望生成器的使用方式如下:

    Dragon dragon = Dragon.builder()
        .dimensions()
            .height(12.0)
            .length(25.0)
            .back()
        .name("Smaug")
        .build();
    
    这就是实现它的方法(使用Lombok 1.18.8):

    维度的生成器保存对容器的引用
    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.builder()
    不再可用,因此不能直接在具有
    Dimensions
    字段的其他类的生成器中使用它。但是,也有一个解决方案:使用
    @SuperBuilder Dimensions
    并定义一个
    类嵌套DimensionsBuilder扩展Dimensions。DimensionsBuilder
    DragonBuilder

    中,主要问题是您在问题末尾描述的问题:它不可缩放。如果您的外部类委托给5个构建器,那么最终将有几十个方法,并且每次更改5个构建器中的任何一个时都必须进行更改。您将忘记将内部构建器中的更改复制到外部构建器。你会有矛盾。比如说,如果你的外部物体需要两个维度,而不是一个维度呢?你违反了单一责任原则。我还发现,这使得外部构建器/类的结构不那么清晰。这也使得调用方更难只传递现有维度对象,或者重用创建维度对象的现有代码。最后一点确实是另一个问题。您仍然可以调用
    Dragon.builder().dimensions(someDimensions).build()
    ,但提供的
    someDimensions
    对象将永远不会被使用。