returning inherited class instead of superclass in method overriding

陌路散爱 提交于 2019-12-23 13:39:42

问题


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.


回答1:


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();



回答2:


I'm not sure if it can be considered as elegant, but you can use casting:

SonBuilder sonBuilder = new SonBuilder();
Son son1 = sonBuilder.sonProperty("aString").build();
Son son2 = (Son) sonBuilder.sonProperty("aString").parentProperty(1).build();
Son son3 = ((SonBuilder) sonBuilder.parentProperty(1)).sonProperty("aString").build();



回答3:


You can test for the parent property to exist in your build() method.

return (parentProperty == null) ? new Parent(parentProperty, sonProperty) : new Son(sonProperty);



回答4:


Override parentProperty in SonBuilder:

@Override
public SonBuilder parentProperty(int parentPropertyValue) {
    super.parentProperty(parentPropertyValue);
    return this;
}

This is safe because (and as long as) super.parentProperty returns this. If you have immutable builders instead, your override would also be different.

Or use generics:

abstract class AParentBuilder<T extends Parent> {
    protected int parentProperty;

    public AParentBuilder<T> parentProperty(int parentPropertyValue) {
        parentPropertyValue = parentPropertyValue;
        return this;
    }

    abstract public T build();
}

class ParentBuilder extends AParentBuilder<Parent> {
    @Override
    public Parent build() {
        return new Parent(parentProperty);
    }
}

abstract class ASonBuilder<T extends Son> extends AParentBuilder<T> {
    private String sonProperty;

    public ASonBuilder<T> sonProperty(String sonProperty) {
        this.sonProperty = sonProperty;
        return this;
    }
}

class SonBuilder extends ASonBuilder<Son> {
    @Override
    public Son build() {
        return new Son(parentProperty, sonProperty);
    }
}



回答5:


An alternative way is using covariant return type in the subclass method.

Keep SonBuilder extend ParentBuilder but override parentProperty() to return a SonBuilder :

@Override
public SonBuilder parentProperty(int parentPropertyValue) {
   super.parentProperty(parentPropertyValue);
   return this;
}

You can still create the Son in this way :

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

as now parentProperty(1) invokes the subclass method that returns a SonBuilder.

This solution may seem fine but it has a drawback.
It creates a strong coupling between the subclass builder and the base builder.
At each time you add a setProperty method in the parent builder, you have to override it in the subclass.
Otherwise, you would still have the initial problem : a code that doesn't compile or it would instantiate the Parent instead of the Son class.
For classes that don't change at all, it may be acceptable.
Otherwise, it should be avoided.

The solution using the composition is better as it allows the SonBuilder to evolve at its own rhythm and with its own rules.



来源:https://stackoverflow.com/questions/47197777/returning-inherited-class-instead-of-superclass-in-method-overriding

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!