Reputation: 7329
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?
Upvotes: 13
Views: 10707
Reputation: 48837
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;
}
}
Upvotes: 4
Reputation: 23562
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.
Upvotes: 7
Reputation: 19702
It's ok, just suppress the warning.
If you must be a purist, here is a solution:
abstract public class BaseBuilder<T...>
{
abstract protected T getThis();
public T buildSomething()
...
return getThis();
...
public class ChildBuilder extends BaseBuilder<ChildBuilder>
{
@Override
protected ChildBuilder getThis(){ return this; }
}
I'd recommend to ditch the recursive bound; it's mostly useless. Just name the type variable This
.
public class BaseBuilder<This>
Upvotes: -1
Reputation: 4214
IMO you base builder signature BaseBuilder<T extends BaseBuilder<T>>
needs to be changed.
I would imagine T to refer to the type being built BaseBuilder<T extends ComplexObject>
and ChildBuilder extends BaseBuilder<MoreComplexObject>
Overriding methods in ChildBuilder still works - you return this
. From the build method you return T
public class BaseBuilder<T extends ComplexObject> {
public BaseBuilder<T> withComplexThing() {
return this;
}
public T build() {
}
}
public class ChildBuilder extends BaseBuilder<MoreComplexObject> {
public ChildBuilder withComplexThing() {
return this;
}
public MoreComplexObject build() {
}
}
Upvotes: -1
Reputation: 18552
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.
}
Upvotes: 1
Reputation: 53565
Instead of declaring the method to return T
- declare it to return BaseBuilder
:
public BaseBuilder buildSomething() {
...
Since T
extends BaseBuilder
- but is still not known during compile-time, I believe that that's the best compromise you can do.
If you know (during compile-time) exactly which type you're returning you can simply return it, but if not - and you'll have to downcast - you will keep getting "Type safety: Unchecked cast
and if you can prove that the downcast is valid that's perfectly fine to SuppressWarnings
.
See Josh Bloch's wise words in regards.
Upvotes: 0