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. 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();