Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 327
J.Steve Avatar asked Jul 09 '15 16:07

J.Steve


3 Answers

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.

like image 126
Dragan Bozanovic Avatar answered Oct 10 '22 08:10

Dragan Bozanovic


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;
  }
}
like image 26
sp00m Avatar answered Oct 10 '22 07:10

sp00m


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.
}
like image 43
Nayuki Avatar answered Oct 10 '22 07:10

Nayuki