从父类返回子类
我有一个构建器类,它从大多数方法返回以允许菊花链。 为了使这个工作与子类,我希望父方法返回子的实例,以便子方法可用于链到最后。
public class BaseBuilder<T extends BaseBuilder> { public T buildSomething() { doSomeWork(); /* OPTION #1: */ return this; // "Type mismatch: cannot convert from BaseBuilder to T" /* OPTION #2: */ return T.this; // "Type mismatch: cannot convert from BaseBuilder to T" /* OPTION #3: */ return (T) this; // "Type safety: Unchecked cast from SqlBuilder to T" } } public class ChildBuilder extends BaseBuilder {}
选项#1和#2导致编译错误,选项#3导致警告(尽管可以使用@SuppressWarnings("unchecked")
抑制@SuppressWarnings("unchecked")
)。 这里有更好的方法吗? 我怎样才能安全地将Basebuilder投降给Childbuilder?
声明ChildBuilder extends BaseBuilder
以某种方式表示代码气味并且似乎违反了DRY。 在这个例子中, BaseBuilder
只能用ChildBuilder
进行参数化,而不是其他任何东西,所以它应该是多余的。
我宁愿重新考虑我是否真的想过度构建这个,我会尝试将子构建器的所有方法都放到BaseBuilder
。 然后我可以简单地从支持链接的所有方法中返回this
。
如果我仍然认为通过将特定的构建器方法组分成它们自己的类来获益,那么我会优先考虑组合,因为不建议仅为代码重用应用inheritance。
假设我们有两个BaseBuilder
子类:
class BuilderA extends BaseBuilder { BuilderA buildSomethingA() { return this; } } class BuilderB extends BaseBuilder { BuilderB buildSomethingB() { return this; } }
如果需要链接buildSomethingA
和buildSomethingB
如果:
builder.buildSomething().buildSomethingA().buildSomethingB();
如果不将子类方法移动到BaseBuilder
,我们将无法做到这BaseBuilder
; 但是想象一下,还有BuilderC
,这些方法没有意义,不应该从BaseBuilder
inheritance。
如果我们仍然将这两个方法移动到超类,下一次将其他三个方法移动到下一个时间……我们最终会得到一个超类,它负责整个层次结构的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
实例,以便您可以链接更多方法或启动新的子构建器。
这种方式(子)构建器可以从它们需要的任何东西inheritance(否则它们必须只扩展BaseBuilder
)并且可以拥有自己有意义的层次结构或组合。
一种可能性是利用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> { // Ugly. public T alpha() { return (T)this; } public T delta() { return (T)this; } } class Child extends Base { // 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中的强制转换是不安全的,因为下面的类将编译(这是开发人员的责任):
public class ChildBuilder extends BaseBuilder {} ^^^^^^^^^^^
一个常见的解决方案是向子类询问它们:
public abstract class BaseBuilder> { protected abstract T getThis(); public T buildSomething() { return getThis(); } } public class ChildBuilder extends BaseBuilder { @Override protected ChildBuilder getThis() { return this; } }
而不是声明方法返回T
– 声明它返回BaseBuilder
:
public BaseBuilder buildSomething() { ...
由于T
扩展了BaseBuilder
– 但在编译期间仍然不知道,我相信这是你可以做的最好的妥协。
如果你知道(在编译期间)确切地说你正在返回哪种类型你可以简单地返回它,但如果没有 – 你将不得不贬低 – 你将继续得到"Type safety: Unchecked cast
如果你能certificate对于SuppressWarnings
,向下转是有效的。
请参阅Josh Bloch关于问题的明智话语 。
IMO基础构建器签名BaseBuilder
需要更改。
我想T会引用正在构建的类型BaseBuilder
而ChildBuilder extends BaseBuilder
ChildBuilder中的覆盖方法仍然有效 – 您将返回this
。 从构建方法返回T
public class BaseBuilder { public BaseBuilder withComplexThing() { return this; } public T build() { } } public class ChildBuilder extends BaseBuilder { public ChildBuilder withComplexThing() { return this; } public MoreComplexObject build() { } }
没关系,只是压制警告。
如果你必须是纯粹主义者,这是一个解决方案:
abstract public class BaseBuilder { abstract protected T getThis(); public T buildSomething() ... return getThis(); ... public class ChildBuilder extends BaseBuilder { @Override protected ChildBuilder getThis(){ return this; } }
我建议抛弃递归界限; 它几乎没用。 只需将类型变量命名为This
。
public class BaseBuilder