使用 Builder 也可以构建封装对象
Using Builder to also build encapsulated object
假设我有一个 class 封装了另一个 class:
@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
.
这样,我可以像这样实例化 class:
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;
添加构建器方法以直接构建内部 class 也是好的编码习惯吗?或者它是否违反了一些设计原则,因为它可能耦合得太紧了?
我已经遇到的一个问题是在对内部 class 进行重构时,我还必须对父 class.
应用大部分相同的重构
在我看来,从 style/design 的角度来看,您的方法没有根本性的错误。但是,正如用户 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):
@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
并在 DragonBuilder
.
中定义一个 class NestedDimensionsBuilder extends Dimensions.DimensionsBuilder<Dimensions, NestedDimensionsBuilder>
假设我有一个 class 封装了另一个 class:
@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
.
这样,我可以像这样实例化 class:
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;
添加构建器方法以直接构建内部 class 也是好的编码习惯吗?或者它是否违反了一些设计原则,因为它可能耦合得太紧了?
我已经遇到的一个问题是在对内部 class 进行重构时,我还必须对父 class.
应用大部分相同的重构在我看来,从 style/design 的角度来看,您的方法没有根本性的错误。但是,正如用户 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):
@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
并在 DragonBuilder
.
class NestedDimensionsBuilder extends Dimensions.DimensionsBuilder<Dimensions, NestedDimensionsBuilder>