简体   繁体   中英

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?

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.
}

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.

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() {
   }
}

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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM