J.Steve
J.Steve

Reputation: 7329

Return child class from parent class

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

Answers (6)

sp00m
sp00m

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

Dragan Bozanovic
Dragan Bozanovic

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

ZhongYu
ZhongYu

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

6ton
6ton

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

Nayuki
Nayuki

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

Nir Alfasi
Nir Alfasi

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

Related Questions