从父类返回子类

13

我有一个构建器类,它在大多数方法中返回自身,以便进行链式调用。为了使其与子类一起使用,我希望父类方法返回子类的实例,以便可以链接到子类的方法。

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

选项 #1 和 #2 会导致编译错误,选项 #3 会有一个警告(但可以通过 @SuppressWarnings("unchecked") 来抑制)。这里是否有更好的方法?如何安全地将 Basebuilder 向下转换为 Childbuilder?


这不应该是public class BaseBuilder<T>吗?为什么泛型类型引用了同一个类?=> ChildBuilder扩展自BaseBuilder<ChildBuilder> - 6ton
@6ton,因为唯一有效的泛型类型是继承 BaseBuilder 的类型。例如 BaseBuilder<String> 是没有意义的。 - J.Steve
仍然不确定为什么需要签名 - 我发布了一篇带有理由的答案。 - 6ton
6个回答

7
声明 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中继承。
即使我们把这两个方法移到超类中,下一次可能又会有三个其他的方法需要移到超类中...最终我们会得到一个负责整个层次结构90%职责的超类,并且还有很多类似这样的代码:
if ((this instanceof BuilderB) && !flag1 && flag2) {
   ...
} else if ((this instanceof BuilderC) && flag1 && !flag2 && thing != null) {
   ...
} else if ...

我更喜欢的解决方案是一种类似 DSL 的方法,例如:
builder.buildSomething1().buildSomething2()
   .builderA()
      .buildSomethingA1().buildSomethingA2()
   .end()
   .buildSomething3()
   .builderB()
      .buildSomethingB()
   .end();

这里end()返回builder实例,因此您可以链接更多它的方法或启动新的子构建器。

这样,(子)构建器可以继承它们需要的任何内容(否则它们必须仅扩展BaseBuilder),并且可以拥有自己的有意义的层次结构或组合。


谢谢您,您提出了一些很好的观点。end()建议是个好主意! - J.Steve

4
选项#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;
  }
}

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

0

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

public BaseBuilder buildSomething() {
...

由于 T 扩展了 BaseBuilder - 但在编译时仍然未知,我认为这是你可以做出的最好妥协。

如果您在编译时确切地知道要返回的类型,可以简单地返回它,但如果不知道 - 并且您必须向下转换 - 您将继续收到 "Type safety: Unchecked cast,如果您可以证明向下转换是有效的,那么使用 SuppressWarnings 就可以了。

请参见 Josh Bloch 在 此处 的明智言辞。


-1

我认为你的基础构建器签名 BaseBuilder<T extends BaseBuilder<T>> 需要更改。

我想 T 应该指正在构建的类型 BaseBuilder<T extends ComplexObject>ChildBuilder extends BaseBuilder<MoreComplexObject>

在 ChildBuilder 中覆盖方法仍然有效 - 你可以返回 this。从 build 方法中,你应该返回 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() {
   }
}

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接