从父类返回子类

时间:2015-07-09 16:23:36

标签: java generics inheritance types casting

我有一个构建器类,它从大多数方法返回以允许菊花链。为了使这个工作与子类,我希望父方法返回子的实例,以便子方法可用于链到最后。

    PackageManager pm = getPackageManager();
    boolean hasGps = pm.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);

选项#1和#2导致编译错误,选项#3导致警告(尽管可以使用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> {} 抑制)。这里有更好的方法吗?我怎样才能安全地将Basebuilder投降给Childbuilder?

6 个答案:

答案 0 :(得分:6)

声明ChildBuilder extends BaseBuilder<ChildBuilder>以某种方式表示代码气味并且似乎违反DRY。在此示例中,BaseBuilder只能使用ChildBuilder进行参数化,而不是其他任何内容,因此它应该是多余的。

我宁愿重新思考我是否真的想过度构建这个,我会尝试将子构建器的所有方法都放到BaseBuilder中。然后我可以简单地从支持链接的所有方法返回this

如果我仍然认为通过将特定的构建器方法组分成他们自己的类来获益,那么我会优先考虑组合,因为不建议仅为代码重用应用继承。

假设我们有两个BaseBuilder的子类:

class BuilderA extends BaseBuilder<BuilderA> {
   BuilderA buildSomethingA() { return this; }
}

class BuilderB extends BaseBuilder<BuilderB> {
   BuilderB buildSomethingB() { return this; }
}

如果需要链buildSomethingAbuildSomethingB如下:

,该怎么办?
builder.buildSomething().buildSomethingA().buildSomethingB();

如果不将子类方法移到BaseBuilder,我们将无法做到这一点;但是想象一下BuilderC也有BaseBuilder这些方法没有意义,不应该从if ((this instanceof BuilderB) && !flag1 && flag2) { ... } else if ((this instanceof BuilderC) && flag1 && !flag2 && thing != null) { ... } else if ... 继承。

如果我们将这两个方法移到超类中,下次又将其他三个方法移动到下一个时间......我们最终会得到一个超级类,负责整个层次结构的90%的职责。代码如:

builder.buildSomething1().buildSomething2()
   .builderA()
      .buildSomethingA1().buildSomethingA2()
   .end()
   .buildSomething3()
   .builderB()
      .buildSomethingB()
   .end();

我更喜欢的解决方案是DSL:

end()

此处builder返回BaseBuilder实例,以便您可以链接更多方法或启动新的子构建器。

这样,(子)构建器可以继承它们需要的任何东西(否则它们只能扩展OleInitialize),并且可以拥有自己有意义的层次结构或组合。

答案 1 :(得分:2)

选项#3中的强制转换是不安全的,因为下面的类会编译(这是开发人员的责任):

public class ChildBuilder extends BaseBuilder<FakeBuilder> {}
                                              ^^^^^^^^^^^

一个常见的解决方案是询问子类的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;
  }
}

答案 2 :(得分:1)

一种可能性是利用Java支持协变返回类型的事实。例如,此代码是合法的:

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

否则,选项#3是真正实现您想要的唯一方法。它允许超类方法直接返回子类类型,以便您可以调用子类方法:

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

答案 3 :(得分:0)

而不是声明方法返回T - 声明它返回BaseBuilder

public BaseBuilder buildSomething() {
...

由于T延伸BaseBuilder - 但在编译期间仍然不知道,我相信这是您可以做的最佳折衷方案。

如果你知道(在编译期间)确切地返回了哪种类型,你可以简单地返回它,但如果没有 - 并且你必须向下转发 - 你将继续获得"Type safety: Unchecked cast如果你能证明这个堕落者是有效的,SuppressWarnings完全没问题。

见Josh Bloch的wise words

答案 4 :(得分:-1)

IMO您的基础构建器签名BaseBuilder<T extends BaseBuilder<T>>需要更改。

我想T会引用正在构建的类型BaseBuilder<T extends ComplexObject>ChildBuilder extends BaseBuilder<MoreComplexObject>

ChildBuilder中的覆盖方法仍然有效 - 您返回this。从构建方法返回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() {
   }
}

答案 5 :(得分:-1)

没关系,只是压制警告。

如果你必须是纯粹主义者,这是一个解决方案:

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

我建议抛弃递归界限;它几乎没用。只需将类型变量命名为This

public class BaseBuilder<This>