使用 Lombok 的 class 层次结构的 Common Builder 基础实例
Common Builder base instance for a class hierarchy using Lombok
我有一个 class 层次结构,其中有一个抽象基础 class 和几个 child class es。基础 class 有 ~25 个字段,每个 child 有一个额外的数字 0-8 字段。
我想使用Builder模式来构造每个child实例,我想尽可能使用Lombok来保持代码简洁。在 this suggestion 之后,我有如下代码:
@AllArgsConstructor
@Data
public abstract class Base {
private int b1, b2, ... , b25;
}
public class C1 extends Base {
private int c11, c12, ... , c16;
@Builder
private C1(int b1, int b2, ..., int b25, int c11, ... int c16) {
super(b1, b2, ...., b25);
this.c11 = c11;
...
this.c16 = c16;
}
}
public class C2 extends Base {
@Builder
private C2(int b1, int b2, ..., int b25) {
super(b1, b2, ...., b25);
}
}
这使得构建 child classes 变得容易
C1 c1 = C1.builder().b1(1).b2(2)....b25(25).c11(101).c12(102).build();
C2 c2 = C2.builder().b1(1).b2(2)....b25(25).build();
问题是每次创建 child class 时都会重复 .b1().b2()...
链式调用。
理想情况下,我想要一种通用的方法来设置 B 值,而不管正在构建哪个 child class。 (假设有另一个名为 BValuesProvider
的 class 可以提供这些值)
public void setBValues(BValuesProvider bv, // what else goes here??? //) {
// something.b1(bv.b1()).b2(bv.b2()) ...
}
public createC1(BValuesProvider bv, c11, c12, ..., c16) {
C1.Builder c1b = C1.builder().c11(c11).c12(c12)....c16(c16);
// Call setBValues somehow
return c1b.build();
}
public createC2(BValuesProvider bv) {
// Call setBValues somehow
return c2b.build();
}
我目前的解决方案是将 @Data
注释附加到基础 class 以公开 setters/getters 所以我的代码如下所示:
public void setBValues(BValuesProvider bv, Base cx) {
cx.setB1(bv.b1());
cx.setB2(bv.b2());
...
cx.setB25(bv.b25());
}
public createC1(BValuesProvider bv, c11, c12, ..., c16) {
C1 c1 = C1.builder().c11(c11).c12(c12)....c16(c16).build();
setBValues(bv, c1);
return c1;
}
public createC2(BValuesProvider bv) {
C2 c2 = C2.builder().build();
setBValues(bv, c2);
return c2;
}
问题:
有更好的方法吗?具体来说,我觉得首先构建一个 child class(完全),然后在其上调用 setBxx()
函数似乎是一个糟糕的模式。公开设置器本身会使 class 非常可变。
SO 上还有关于 builders/inheritance
的其他问题
- Builder Pattern and Inheritance
- Subclassing a Java Builder class
- how to Call super constructor in Lombok
然而 none 他们谈论有一个 "base builder" 每个 child
builder是一个sub-class的。所以,我无法弄清楚使用泛型,
setBValues
函数的第二个参数应该是什么。
- 我也尝试了 Lombok 的
@Superbuilder
注释,但同样,虽然它大大简化了代码,但我仍然不知道如何获得基础构建器。
这可以使用(实验性的)@SuperBuilder
注释和 lombok >= 1.18.4 来实现。您可以通过添加一个方法来自定义 Base
的 @SuperBuilder
,该方法将 BValuesProvider
作为参数并设置该参数的所有值:
@SuperBuilder
public abstract class Base {
public static abstract class BaseBuilder<C extends Base, B extends BaseBuilder<C, B>> {
public B fillFromProvider(BValuesProvider bv) {
b1(bv.b1());
b2(bv.b2());
...
return self();
}
}
...
}
然后你可以像这样使用它(其中 bv
是一个 BValuesProvider
实例):
C1 c1 = C1.builder().fillFromProvider(bv).c11(11).build();
OP在这里。虽然这不是我提出的确切问题的答案,但我想分享一个仍然使用 Lombok 但不使用 @Builder 的替代方案。
@Getter
public abstract class Base<T extends Base> {
private int b1, b2, ... , b25;
private T cast() { return (T) this; }
public T setB1(final int b1) { this.b1 = b1; return cast(); }
public T setB2(final int b2) { this.b2 = b2; return cast(); }
...
}
@Getter @Setter @Accessors(chain = true)
public class C1 extends Base<C1> {
private int c11, c12, ... , c16;
public static C1 init() { return new C1(); }
private C1() {}
}
@Getter @Setter @Accessors(chain = true)
public class C2 extends Base<C2> {
public static C2 init() { return new C2(); }
private C2() {}
}
我刚刚泛化了基础 class 并在子 classes 上使用了链式访问器。然后调用者将被修改为:
public void setBValues(BValuesProvider bv, Base cx) {
cx.setB1(bv.b1())
.setB2(bv.b2())
...
.setB25(bv.b25());
}
public createC1(BValuesProvider bv, c11, c12, ..., c16) {
C1 c1 = C1.init().setC11(c11)....setC16(c16);
setBValues(bv, c1);
return c1;
}
public createC2(BValuesProvider bv) {
C2 c2 = C2.init();
setBValues(bv, c2);
return c2;
}
优点:
- 我的代码需要不了解
BValuesProvider
- 设置公共基本参数对我来说以那种链式方式看起来更自然
缺点:
- base class 有点冗长,但总的来说,它还不错,因为我们避免了每个子 class 中的大构造函数。
- 这不是真正的构建器模式,因为我们正在创建对象并在其上执行一系列 set() 与在其上执行
.builder().x(x).y(y).build()
。
我有一个 class 层次结构,其中有一个抽象基础 class 和几个 child class es。基础 class 有 ~25 个字段,每个 child 有一个额外的数字 0-8 字段。
我想使用Builder模式来构造每个child实例,我想尽可能使用Lombok来保持代码简洁。在 this suggestion 之后,我有如下代码:
@AllArgsConstructor
@Data
public abstract class Base {
private int b1, b2, ... , b25;
}
public class C1 extends Base {
private int c11, c12, ... , c16;
@Builder
private C1(int b1, int b2, ..., int b25, int c11, ... int c16) {
super(b1, b2, ...., b25);
this.c11 = c11;
...
this.c16 = c16;
}
}
public class C2 extends Base {
@Builder
private C2(int b1, int b2, ..., int b25) {
super(b1, b2, ...., b25);
}
}
这使得构建 child classes 变得容易
C1 c1 = C1.builder().b1(1).b2(2)....b25(25).c11(101).c12(102).build();
C2 c2 = C2.builder().b1(1).b2(2)....b25(25).build();
问题是每次创建 child class 时都会重复 .b1().b2()...
链式调用。
理想情况下,我想要一种通用的方法来设置 B 值,而不管正在构建哪个 child class。 (假设有另一个名为 BValuesProvider
的 class 可以提供这些值)
public void setBValues(BValuesProvider bv, // what else goes here??? //) {
// something.b1(bv.b1()).b2(bv.b2()) ...
}
public createC1(BValuesProvider bv, c11, c12, ..., c16) {
C1.Builder c1b = C1.builder().c11(c11).c12(c12)....c16(c16);
// Call setBValues somehow
return c1b.build();
}
public createC2(BValuesProvider bv) {
// Call setBValues somehow
return c2b.build();
}
我目前的解决方案是将 @Data
注释附加到基础 class 以公开 setters/getters 所以我的代码如下所示:
public void setBValues(BValuesProvider bv, Base cx) {
cx.setB1(bv.b1());
cx.setB2(bv.b2());
...
cx.setB25(bv.b25());
}
public createC1(BValuesProvider bv, c11, c12, ..., c16) {
C1 c1 = C1.builder().c11(c11).c12(c12)....c16(c16).build();
setBValues(bv, c1);
return c1;
}
public createC2(BValuesProvider bv) {
C2 c2 = C2.builder().build();
setBValues(bv, c2);
return c2;
}
问题:
有更好的方法吗?具体来说,我觉得首先构建一个 child class(完全),然后在其上调用
setBxx()
函数似乎是一个糟糕的模式。公开设置器本身会使 class 非常可变。SO 上还有关于 builders/inheritance
的其他问题- Builder Pattern and Inheritance
- Subclassing a Java Builder class
- how to Call super constructor in Lombok
然而 none 他们谈论有一个 "base builder" 每个 child builder是一个sub-class的。所以,我无法弄清楚使用泛型,
setBValues
函数的第二个参数应该是什么。- 我也尝试了 Lombok 的
@Superbuilder
注释,但同样,虽然它大大简化了代码,但我仍然不知道如何获得基础构建器。
这可以使用(实验性的)@SuperBuilder
注释和 lombok >= 1.18.4 来实现。您可以通过添加一个方法来自定义 Base
的 @SuperBuilder
,该方法将 BValuesProvider
作为参数并设置该参数的所有值:
@SuperBuilder
public abstract class Base {
public static abstract class BaseBuilder<C extends Base, B extends BaseBuilder<C, B>> {
public B fillFromProvider(BValuesProvider bv) {
b1(bv.b1());
b2(bv.b2());
...
return self();
}
}
...
}
然后你可以像这样使用它(其中 bv
是一个 BValuesProvider
实例):
C1 c1 = C1.builder().fillFromProvider(bv).c11(11).build();
OP在这里。虽然这不是我提出的确切问题的答案,但我想分享一个仍然使用 Lombok 但不使用 @Builder 的替代方案。
@Getter
public abstract class Base<T extends Base> {
private int b1, b2, ... , b25;
private T cast() { return (T) this; }
public T setB1(final int b1) { this.b1 = b1; return cast(); }
public T setB2(final int b2) { this.b2 = b2; return cast(); }
...
}
@Getter @Setter @Accessors(chain = true)
public class C1 extends Base<C1> {
private int c11, c12, ... , c16;
public static C1 init() { return new C1(); }
private C1() {}
}
@Getter @Setter @Accessors(chain = true)
public class C2 extends Base<C2> {
public static C2 init() { return new C2(); }
private C2() {}
}
我刚刚泛化了基础 class 并在子 classes 上使用了链式访问器。然后调用者将被修改为:
public void setBValues(BValuesProvider bv, Base cx) {
cx.setB1(bv.b1())
.setB2(bv.b2())
...
.setB25(bv.b25());
}
public createC1(BValuesProvider bv, c11, c12, ..., c16) {
C1 c1 = C1.init().setC11(c11)....setC16(c16);
setBValues(bv, c1);
return c1;
}
public createC2(BValuesProvider bv) {
C2 c2 = C2.init();
setBValues(bv, c2);
return c2;
}
优点:
- 我的代码需要不了解
BValuesProvider
- 设置公共基本参数对我来说以那种链式方式看起来更自然
缺点:
- base class 有点冗长,但总的来说,它还不错,因为我们避免了每个子 class 中的大构造函数。
- 这不是真正的构建器模式,因为我们正在创建对象并在其上执行一系列 set() 与在其上执行
.builder().x(x).y(y).build()
。