Search code examples
javainheritancebuilderfluentoverriding

returning inherited class instead of superclass in method overriding


I have a certain class structure that looks something like this:

class Parent {
    public Parent(int property) { /* use property */}
}
class Son extends Parent {
    public Son(int parentProperty, String sonProperty) { 
        super(parentProperty);
        /* use son property */ 
    }
}

I'd like to create builders for both these classes such that:

class ParentBuilder {
    protected int parentProperty;

    public ParentBuilder parentProperty(int parentPropertyValue) {
        parentPropertyValue = parentPropertyValue;
        return this;
    }

    public Parent build() {
        return new Parent(parentProperty);
    }
}
class SonBuilder extends ParentBuilder {
    private String sonProperty;

    public SonBuilder sonProperty(String sonProperty) {
        this.sonProperty = sonProperty;
        return this;
    }

    @Override
    public Son build() {
        return new Son(parentProperty, sonProperty);
    }
}

but this causes the following problem:

SonBuilder sonBuilder = new SonBuilder();
sonBuilder.sonProperty("aString").build(); // this works and creates Son
sonBuilder.sonProperty("aString").parentProperty(1).build(); // this works and creates Parent instead of Son
sonBuilder.parentProperty(1).sonProperty("aString").build(); // this doesn't work

I realize I'm nitpicking and this could be solved with just not returning this (i.e. without method chaining), but I'm wondering if there is an elegant solution.

edit

It seems that the word "elegant" is a source for a bit of confusion.

By "elegant" I mean a solution which allows for method chaining and does not involve casting.


Solution

  • First point

    sonBuilder.sonProperty("aString").parentProperty(1).build();
    

    this works and creates Parent instead of Son

    It is expected as parentProperty() returns a ParentBuilder :

    public ParentBuilder parentProperty(int parentPropertyValue) {...
    

    And ParentBuilder.build() creates a Parent :

    public Parent build() {
        return new Parent(parentProperty);
    }
    

    Second point

    sonBuilder.parentProperty(1).sonProperty("aString").build(); // this doesn't work
    

    As said in the first point, parentProperty() returns a ParentBuilder.
    And ParentBuilder of course doesn't have a sonProperty() method.
    So it cannot compile.

    I'm wondering if there is an elegant solution.

    An elegant solution would be not make SonBuilder inherited ParentBuilder but compose with a ParentBuilder field. For example :

    class SonBuilder {
    
        private String sonProperty;
        private ParentBuilder parentBuilder = new ParentBuilder();
    
        public SonBuilder sonProperty(String sonProperty) {
          this.sonProperty = sonProperty;
          return this;
        }
    
        public SonBuilder parentProperty(int parentPropertyValue) {
          parentBuilder.parentProperty(parentPropertyValue);
          return this;
        }
    
        public Son build() {
          return new Son(parentBuilder.parentProperty, sonProperty);
        }
    }
    

    You can so create the Son in this way :

    SonBuilder sonBuilder = new SonBuilder();
    Son son = sonBuilder.sonProperty("aString").parentProperty(1).build();