Reputation: 677
I have a class hierarchy with a single abstract base class and several child classes. The base class has ~25 fields and each child has an additional number 0-8 fields.
I would like to use the Builder pattern to construct each child instance and I'd like to use Lombok as much as possible to keep the code concise. Following this suggestion I have the code as below:
@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);
}
}
This makes it easy to construct the child classes as
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();
The problem is that the the .b1().b2()...
chained calls are repeated every time any child class is created.
Ideally, I want a common way to set the B values regardless of which child class is being built. (Let's assume there's another class called BValuesProvider
that can supply those values)
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();
}
My current solution has been to attach the @Data
annotation to the base class to expose setters/getters so my code looks like this:
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;
}
Questions:
Is there a better way to do this? Specifically, I feel that first building a child class (fully) and then calling setBxx()
functions on it seems like a bad pattern. Exposing the setters itself makes the class quite mutable.
There have been other questions on SO about builders/inheritance
However none of them talk about having a "base builder" that each child
builder is a sub-class of. So, I can't figure out using generics,
what the second argument to the setBValues
function should be.
@Superbuilder
annotation but again, while it greatly simplifies the code, I still don't see how to get a base builder. Upvotes: 3
Views: 1411
Reputation: 2253
I got it working for me by creating a method that builds and return the parent class and then I cast my child class to the parent class and continue with setting the fields.
public Child toChild() {
return ((Child) toBaseRequest())
.toBuilder()
.someOtherFieldFromChild(1)
.build();
}
private Parent toBaseRequest() {
return Parent.builder()
.fooBar(new FooBar())
.build();
}
make sure both children and parent classes have
@SuperBuilder(toBuilder = true)
Upvotes: 0
Reputation: 677
OP here. While this isn't an answer to the exact question I asked, I wanted to share an alternative that still uses Lombok but not @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() {}
}
I've just generified the base class and used a chained accessor on the child classes. The callers would then be modified as:
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;
}
Advantages:
BValuesProvider
Disadvantages:
.builder().x(x).y(y).build()
on it.Upvotes: 0
Reputation: 8042
This can be achieved using the (experimental) @SuperBuilder
annotation and lombok >= 1.18.4. You can customize the @SuperBuilder
of Base
by adding a method that takes a BValuesProvider
as argument and sets all values from that:
@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();
}
}
...
}
Then you can use it like this (where bv
is a BValuesProvider
instance):
C1 c1 = C1.builder().fillFromProvider(bv).c11(11).build();
Upvotes: 2