在方法重写中返回继承的类而不是超类

时间:2017-11-09 09:08:26

标签: java inheritance builder fluent method-overriding

我有一个看起来像这样的类结构:

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

我想为这两个类创建构建器,以便:

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

但这会导致以下问题:

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

我意识到我在挑剔,这可以通过不返回this来解决(即没有方法链接),但我想知道是否有一个优雅的解决方案。

修改

似乎“优雅”这个词有点混乱。

“优雅”是指一种允许方法链接而不涉及投射的解决方案。

5 个答案:

答案 0 :(得分:5)

第一点

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

这可以创建Parent而不是Son

预计parentProperty()会返回ParentBuilder

public ParentBuilder parentProperty(int parentPropertyValue) {...

ParentBuilder.build()创建Parent

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

第二点

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

如第一点所述,parentProperty()会返回ParentBuilder ParentBuilder当然没有sonProperty()方法 所以它无法编译。

  

我想知道是否有一个优雅的解决方案。

优雅的解决方案不是让SonBuilder继承ParentBuilder,而是由ParentBuilder字段组成。 例如:

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

您可以这样创建Son

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

答案 1 :(得分:0)

您可以测试build()方法中存在的父属性。

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

答案 2 :(得分:0)

我不确定它是否可以被视为优雅,但您可以使用强制转换:

template <class T>
struct tag { 
    tag(T) {}
};

int main() {
              tag(int{});     //#1
    auto t1 = tag(int{});     //#2
    auto t3 = (tag(int{}));   //#3
}

答案 3 :(得分:0)

覆盖parentProperty中的SonBuilder

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

这是安全的,因为(只要)super.parentProperty会返回this。如果你有不可变的构建器,你的覆盖也会有所不同。

或使用泛型:

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

答案 4 :(得分:0)

另一种方法是在子类方法中使用协变返回类型。

保持SonBuilder延长ParentBuilder,但覆盖parentProperty()以返回SonBuilder

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

你仍然可以用这种方式创建儿子:

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

现在parentProperty(1)调用返回SonBuilder的子类方法。

这个解决方案可能看起来不错,但它有一个缺点。
它在子类构建器和基础构建器之间创建了强大的耦合 每次在父构建器中添加setProperty方法时,都必须在子类中重写它 否则,您仍然会遇到初始问题:一个不编译的代码,或者它将实例化Parent而不是Son类。
对于完全没有变化的课程,可以接受 否则,应该避免。

使用合成的解决方案更好,因为它允许SonBuilder按照自己的节奏和自己的规则进化。