I have a builder class that returns itself from most methods to allow for daisy-chaining. To make this work with child classes, I want the parent methods to return instances of the child so that child methods will be available to chain to the end.
public class BaseBuilder<T extends BaseBuilder<T>> {
public T buildSomething() {
doSomeWork();
/* OPTION #1: */ return this; // "Type mismatch: cannot convert from BaseBuilder<T> to T"
/* OPTION #2: */ return T.this; // "Type mismatch: cannot convert from BaseBuilder<T> to T"
/* OPTION #3: */ return (T) this; // "Type safety: Unchecked cast from SqlBuilder<T> to T"
}
}
public class ChildBuilder extends BaseBuilder<ChildBuilder> {}
Options #1 and #2 result in compilation errors, and option #3 a warning (albeit one that can be suppressed with @SuppressWarnings("unchecked")
). Is there a better approach here? How can I safely downcast Basebuilder to Childbuilder?
The declaration ChildBuilder extends BaseBuilder<ChildBuilder>
somehow indicates a code smell and seems to violate DRY. In this example BaseBuilder
can be parametrized only with ChildBuilder
and nothing else, so it should be redundant.
I would rather rethink whether I really want to over-architecture this and I would try to put all the methods from child builders into the BaseBuilder
. Then I can simply return this
from all the methods supporting chaining.
If I still think that I will benefit by separating specific groups of builder methods into their own classes, then I would give preference to composition, because applying inheritance only for code reuse is not recommended.
Suppose we have two subclasses of the BaseBuilder
:
class BuilderA extends BaseBuilder<BuilderA> {
BuilderA buildSomethingA() { return this; }
}
class BuilderB extends BaseBuilder<BuilderB> {
BuilderB buildSomethingB() { return this; }
}
What if the need arises to chain buildSomethingA
and buildSomethingB
like:
builder.buildSomething().buildSomethingA().buildSomethingB();
We will not be able to do it without moving the subclass methods to the BaseBuilder
; but imagine there is also BuilderC
for which those methods don't make sense and shouldn't be inherited from the BaseBuilder
.
If we nevertheless move these two methods to the superclass, and next time three other methods and next time... we'll end up with a superclass responsible for 90% of the duties of the entire hierarchy with plenty of code like:
if ((this instanceof BuilderB) && !flag1 && flag2) {
...
} else if ((this instanceof BuilderC) && flag1 && !flag2 && thing != null) {
...
} else if ...
The solution I like more is a DSL like:
builder.buildSomething1().buildSomething2()
.builderA()
.buildSomethingA1().buildSomethingA2()
.end()
.buildSomething3()
.builderB()
.buildSomethingB()
.end();
Here end()
returns the builder
instance so you can chain more of its methods or start a new sub-builder.
This way the (sub)builders can inherit from whatever they need to (otherwise they must extend only the BaseBuilder
) and can have their own meaningful hierarchies or compositions.
Cast in option #3 is not safe since the following class would compile (it's the developer responsibility):
public class ChildBuilder extends BaseBuilder<FakeBuilder> {}
^^^^^^^^^^^
A common solution is to ask the subclasses for their this
:
public abstract class BaseBuilder<T extends BaseBuilder<T>> {
protected abstract T getThis();
public T buildSomething() {
return getThis();
}
}
public class ChildBuilder extends BaseBuilder<ChildBuilder> {
@Override
protected ChildBuilder getThis() {
return this;
}
}
One possibility is to utilize the fact that Java supports covariant return types. For example, this code is legal:
class BaseBuilder {
BaseBuilder buildSomething() { (...) return this; }
}
class ChildBuilder extends BaseBuilder {
@Override // Notice the more specific return type
ChildBuilder buildSomething() { (...) return this; }
}
void main() {
BaseBuilder x = new BaseBuilder ().buildSomething().anotherOperation();
ChildBuilder y = new ChildBuilder().buildSomething().anotherOperation();
}
Otherwise, option #3 is the only way to really achieve what you want. It allows superclass methods to directly return a subclass type so that you can invoke subclass methods:
@SuppressWarnings("unchecked") // Ugly.
class Base<T extends Base<T>> { // Ugly.
public T alpha() { return (T)this; }
public T delta() { return (T)this; }
}
class Child extends Base<Child> { // Clean.
// No need to override/redefine alpha() and delta() in child.
public Child gamma() { return this; }
}
void main(String[] args) {
Child x = new Child();
x.alpha().gamma(); // This works because alpha() returns Child.
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With