【问题标题】:How can I build an interface hierarchy for my fluent API?如何为我的 fluent API 构建接口层次结构?
【发布时间】:2013-05-14 21:28:32
【问题描述】:

我正在开发一个流畅的 API,并试图利用 Java 的通用方法来提供一个优雅的 API,为我的用户处理类型转换。由于类型擦除,我在让它工作时遇到了一些麻烦。

这是我的界面的简化版本,显示了我遇到的问题:

interface Query<T extends Query<T>> {
  T execute();
  Query<T> appendClause();
}

interface A<T extends A> extends Query<T> { }

class AImpl implements A<A> {
  A execute() { ... }
  Query<A> appendClause() { ... }
}

AImpl.appendClause() 出现错误。编译器说A 不在其范围内,应该扩展Query。据我所知,我声明AImpl 实现A&lt;A&gt; 意味着A 确实 扩展Query&lt;A&gt;

按照此处的另一个答案,我尝试通过将AImpl 更改为:

class AImpl implements A<AImpl> {
  AImpl execute() { ... }
  Query<AImpl> appendClause() { ... }
}

现在我在编译 A 时遇到错误,提示“类型参数 T 不在其范围内”。

有人对如何处理这个问题有任何建议吗? Java 的泛型让我头疼。

编辑

我已将 A 的定义更改为

interface A<T extends A<T>> extends Query<T> { }

这让 AImpl 的第二个实现开始工作了。但我还想扩展 API,使其能够查询 A 的子类:

interface B<T extends B> extends A<B> { }

class BImpl implements B<BImpl> {
  BImpl execute() { ... }
  Query<BImpl> appendClause() { ... }
}

这个定义让我在B 的声明中出错:“类型参数 B 不在其范围内;应该扩展 A”。

我可以通过将 B 更改为来清除该错误

interface B<T extends B<T>> extends A<B<T>> { }

但是现在我的接口定义开始看起来很荒谬,我觉得我做错了什么。另外,我仍然在 BImpl 中遇到错误:“BImpl 中的 appendClause() 无法在 Query 中实现 appendClause();尝试使用不兼容的返回类型”。

关于如何清理我的子类定义以便我不需要在 extends 中指定整个继承层次结构或如何让 BImpl 工作的任何建议?

编辑 2

好的,我遇到了另一个问题。我有一个生成查询的工厂类:

public class QueryFactory {
  public static <T extends Query<T>> Query<T> queryForType(Class<T> type) { ... }
}

和客户端代码:

Query<B> bQuery = QueryFactory.queryForType(B.class);

我的客户端代码在 bQuery 的声明中给了我一个错误:“类型参数 'B' 不在其范围内;应该扩展 'Query'”。在这一点上,我认为 B 确实扩展了 Query...

如果我将 queryForType() 调用更改为

,此错误就会消失
Query<? extends B> bQuery = QueryFactory.queryForType(B.class);

但我仍然收到来自编译器的未经检查的警告:

unchecked method invocation: <T>queryForType(Class<T>) in QueryFactory is applied to Class<B>
unchecked conversion found: Query required: Query<B>

看起来类型擦除再次与我作斗争,但我不明白这些警告。还有什么建议可以让我重回正轨吗?谢谢!

编辑 3

如果我将客户端代码更改为

,我可以在没有警告的情况下编译它
Query<BImpl> bQuery = QueryFactory.queryForType(BImpl.class);

但我真的很想对 API 用户隐藏实现类。我尝试将 B 设为抽象类而不是接口,以防问题与此有关,但没有帮助。

【问题讨论】:

  • Query> 类型应该代表什么?我认为签名没有意义。例如我希望 Query&lt;Apple&gt; 返回 Apple 的实例,而不是 execute()Query&lt;Apple&gt; 实例?
  • Apple 扩展了 Query。在这种情况下,execute() 返回一个 Apple,您可以在 Apple 上调用 appendClause() 来查找更具体的 Apple。
  • 让您阅读这篇文章了吗unquietcode.com/blog/2011/programming/…
  • 感谢@Greg 的解释。为一个有趣的问题 +1。

标签: java generics fluent-interface


【解决方案1】:

在这里,您的接口A 声明了类型参数T,它扩展了原始 类型A。你应该试试

//                      v--- Add this
interface A<T extends A<T>> extends Query<T> { }

这确保T 使用T 扩展了泛型A。这样,T 将在Query 接口中指定的范围内。

这将适用于实现A&lt;AImpl&gt;AImpl 的第二个版本。

编辑

不得不说

interface B<T extends B<T>> extends A<B<T>> { }

看起来过于复杂。对于B 接口,从A 扩展它,就像你从Query 扩展A 一样:

//                                    v-- Don't mention B here
interface B<T extends B<T>> extends A<T> { }

那么您的 BImpl 课程可能与您的 AImpl 课程相似:

class BImpl implements B<BImpl> {
    public BImpl execute() { ... }
    public Query<BImpl> appendClause() { ... }
}

【讨论】:

  • 好的,这消除了 A 上的错误。我仍然遇到尝试将 A 子类化的问题。编辑我的问题以反映新问题。
  • 我已经修改了我的答案以回应您修改后的问题。
  • 我在尝试添加工厂类来生成查询时遇到了另一个问题。我已经解决了它,并在另一个答案中记录了该解决方案。
猜你喜欢
  • 2011-08-10
  • 2011-01-21
  • 2014-12-07
  • 1970-01-01
  • 1970-01-01
  • 2013-02-21
  • 2013-12-11
  • 1970-01-01
  • 2022-12-31
相关资源
最近更新 更多