【问题标题】:returning inherited class instead of superclass in method overriding在方法覆盖中返回继承的类而不是超类
【发布时间】:2017-11-09 09:08:25
【问题描述】:

我有一个类似这样的类结构:

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(即没有方法链接)来解决,但我想知道是否有一个优雅的解决方案。

编辑

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

“优雅”是指允许方法链接且不涉及强制转换的解决方案。

【问题讨论】:

  • 我是一个 C++ 人,所以不确定,但你的第三行真的会创建一个 Parent 对象吗?还是 Son 对象并将其作为 Parent 引用返回?
  • @king_nak 是的,我实际上是在询问返回this 时是否可以返回当前类(ParentSon)。没有强制转换,也没有放弃方法链接。

标签: java inheritance builder fluent overriding


【解决方案1】:

第一点

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

这工作并创建父而不是儿子

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

【讨论】:

  • 我喜欢它,有点额外的编码,但它是类型安全的并且没有功能泄漏,会使用。
  • 这是它的一大优势:建造者之间没有泄漏/耦合。这个另一个答案stackoverflow.com/a/47198377/270371 提供了一种更短的方法,但有这个缺点。我想根据班级稳定性的水平,第一个或第二个都可以。
【解决方案2】:

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

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】:

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

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

【讨论】:

  • 那会是“优雅”吗?
  • 父属性将永远存在,它是ParentBuilder 的一部分,问题是当我通过parentProperty(...) 设置parentProprerty 时,我得到的是ParentBuilder 而不是SonBuilder
【解决方案4】:

SonBuilder 中覆盖parentProperty

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

【讨论】:

    【解决方案5】:

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

    保留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 以自己的节奏和自己的规则发展。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-04
      • 1970-01-01
      • 2012-11-20
      • 1970-01-01
      • 1970-01-01
      • 2012-09-27
      • 2023-03-16
      • 1970-01-01
      相关资源
      最近更新 更多