It's not possible to construct instances of different classes using the same Builder. As @RDK has pointed out in the comments you have to implement the Builder pattern for each of your classes.
Builder might have the same hierarchy as the classes they are meant to instantiate. That would allow to reuse the functionality of the parent-builder.
Below, I've provided a hierarchy of Builders inspired by the classic implementation from the "Effective Java" by Joshua Bloch (a former Sun Microsystems employee, who led the design and implementation of many Java platform features, including the Java Collections Framework).
Firstly, it's worth noticing that using Builder with inheritance adds a bit of complexity because methods all methods of the Builder should be self-returning, i.e. in order to chain methods each method except build()
returns this
instance of Builder. But it should a concrete Builder, not it's parent type (because parent might be unaware of all the methods that child has declared).
The answer to this problem would be a self-referencing generic type, in the "Effective Java" a generic type with a recursive type parameter. Don't get intimidated, pause for a second and try to recall how java.lang.Enum
looks like. Enum<E extends Enum<E>>
- that's an example of the self-reference, recursively pointing at a particular subclass of java.lang.Enum
.
Similarly, we need a generic builder referencing to itself ClientBuilder<T extends ClientBuilder<T>>
and declaring a self-returning abstract method self()
(which return an instance of T
) that would be implemented by every concrete class.
Note: there's no need in enum ClientType
to differentiate between ClientA
and ClientB
, since they are already different classes.
That how implementation of an AbstractClient
and two concrete client, each having static nested Builder might look like.
Abstract classes AbstractClient
+ ClientBuilder
public abstract class AbstractClient {
protected ClientType type;
protected A a;
protected B b;
protected AbstractClient(ClientBuilder<?> builder) {
this.a = builder.a;
this.b = builder.b;
}
public static abstract class ClientBuilder<T extends ClientBuilder<T>> {
protected A a;
protected B b;
public T withFieldA(A a) {
this.a = a;
return self();
}
public T withFieldB(B b) {
this.b = b;
return self();
}
// abstract methods
public abstract AbstractClient build(); // returns a concrete Client
public abstract T self(); // returns a concrete implementation of ClientBuilder
}
}
Concrete classes ClientA
+ ABuilder
public class ClientA extends AbstractClient {
private ClientA(ABuilder builder) {
super(builder);
}
// other constructors, getters, etc
public static ABuilder builder() {
return new ABuilder();
}
public static class ABuilder extends AbstractClient.ClientBuilder<ABuilder> {
@Override
public ClientA build() {
return new ClientA(this);
}
@Override
public ABuilder self() {
return this; // simply returning This instance of Builder
}
}
}
Concrete classes ClientB
+ BBuilder
public static class ClientB extends AbstractClient {
protected C c;
protected D d;
private ClientB(BBuilder builder) {
super(builder);
this.c = builder.c;
this.d = builder.d;
}
// other constructors, getters, etc
public static BBuilder builder() {
return new BBuilder();
}
public static class BBuilder extends AbstractClient.ClientBuilder<BBuilder> {
protected C c;
protected D d;
public BBuilder withFieldC(C c) {
this.c = c;
return self();
}
public BBuilder withFieldD(D d) {
this.d = d;
return self();
}
@Override
public ClientB build() {
return new ClientB(this);
}
@Override
public BBuilder self() {
return this; // simply returning This instance of Builder
}
}
}
Usage example (dummy classes A, B, C, D are not exposed, use the link below to see the code in action):
public static void main(String[] args) {
ClientA clientA = ClientA.builder()
.withFieldA(new A("1"))
.withFieldB(new B("Alice"))
.build();
ClientB clientB = ClientB.builder()
.withFieldA(new A("2"))
.withFieldB(new B("Bob"))
.withFieldC(new C("admin"))
.withFieldD(new D("fooBar"))
.build();
System.out.println(clientA);
System.out.println(clientB);
}
Output:
ClientA{ a=A[a=1], b=B[b=Alice] }
ClientB{ a=A[a=2], b=B[b=Bob], c=C[c=admin], d=D[d=fooBar] }
A link to Online Demo.