6

我在某处看到了这种模式:

class A extends B<A> {

}

通过将新类型指定为泛型的实际类型来扩展泛型,这种结构有点不寻常。有什么用?这种模式有名字吗?有没有替代模式?

示例:https ://code.google.com/p/selenium/wiki/LoadableComponent

跳到:public class EditIssue extends LoadableComponent<EditIssue> {

编辑:阅读回复后,我似乎需要改变我对编译器类型检查的理解。在我的脑海里,我对这种模式的不满是,如果两个 A 需要相同,那么有没有办法不重复它们?但似乎没有更好的方法将派生类的类型传播给父类。

4

5 回答 5

4

当然,OOP 的答案AB. If Aare not a BthanA应该仅仅用 aB来组合自己来利用B' 的功能。

大概B也有一些通用实现,它们利用了对泛型类型的限制。

另一个用例是B看起来像:

abstract class B<T extends B<T>> {
    public T createCopy(T t);
}

现在子类可以实现createCopy并且客户端代码可以安全地使用它而无需强制转换......例如

class A extends B<A> {
    public A createCopy(A t) {
        return new A(t); //copy constructor
    }
}

将上述内容与以下内容进行比较:

abstract class B {
    public B createCopy(B t);
}
class A extends B {
    public B createCopy(B t) { //Is the copy an A or a different subtype of B? We don't know.
        return new A(t); //copy constructor
    }
}
于 2013-06-14T00:27:16.237 回答
4

在处理递归数据结构时,您可能会这样做。例如,图或树中的节点可以定义为其他节点的集合:

class Node extends AbstractList<Node> {
    ...
}

同样,如果抽象/通用类型用于比较类似类型的对象,您可能会看到类似这样的内容,例如java.lang.Comparable

class MyObject implements Comparable<MyObject> {
    public int compareTo(MyObject other) { ... }
}
于 2013-06-14T00:10:28.693 回答
2

这确实令人困惑,因为这两种类型似乎相互依赖而存在AB<A>这在普通的 OOP 中没有多大意义,那它有什么用呢?我为这种模式找到了 3 个用例。

组合变成继承

假设 aNode 有一个子节点列表。通常的设计是通过组合

class Node
    ArrayList<Node> children = ...

有时为了小的性能提升,人们使用继承代替

class Node extends ArrayList<Node>
    // the super class represents the children...

这有点令人困惑,但没有什么难以理解的。我们知道这只是一种方便,它不会试图传达一个节点是一个节点列表。

LoadableComponent可以考虑这个用例。可以说,它是一种不如组合方法理想的设计

class ComponentLoader<C>
    C get(){...}

class EditIssue
    final ComponentLoader<EditIssue> loader = new ComponentLoader<EditIssue>(){
        @Override void load(){...}
        @Override void isLoaded(){...}
    };

EditIssue compo = ...
compo.loader.get().doSomething();

设计者可能会发现这种方法更笨拙。

方法链

而不是写

foo.doA();
foo.doB();

很多人宁愿写

foo.doA().doB();

不幸的是,该语言并不直接支持方法链接,尽管它正在成为一种越来越受欢迎的功能。解决方法是doA()返回foo。它有点脏但可以接受。

但是,如果foo在类型层次结构中,则解决方法被破坏

class Bar
    Bar doA()

class Foo extends Bar
    Foo doB();

foo.doA().doB(); // doesn't compile, since doA() returns Bar

所以有人呼吁特殊的“自我型”来解决这个问题。假设有一个关键字This代表“自我类型”

class Bar
    This doA()

foo.doA().doB(); // works, doA() returns the type of foo, which is Foo

看来方法链接是“自我类型”的唯一用例,所以语言可能永远不会引入它(最好直接支持方法链接)

人们发现泛型为这个问题提供了一种解决方法

class Bar<This>
    This doA()

class Foo extends Bar<Foo>

Foo has a method "Foo doA()", inherited from Bar<Foo>

这是该A extends B<A>模式最流行的用例。这是一个孤立的解决方法/技巧。它没有在 A 和 B 之间的关系中添加语义。

约束也是一种流行的This做法

class Bar<This extends Bar<This>>

它丑陋且无用,我强烈建议不要使用它。只需使用“This”作为约定来指示它的用途。

LoadableComponent也可能属于这个用例。在更简单的设计中,我们可以做

class LoadableComponent
    void ensureLoaded()

class EditIssue extends LoadableComponent

EditIssue compo = ...
compo.ensureLoaded();
compo.doSomething();

为了支持最后两行的方法链接,LoadableComponent设计成现在的形式,这样我们就可以写compo.get().doSomething()

更多元的东西

所以前两个用例是一种黑客攻击。如果A和之间存在真正的约束B<A>怎么办?

与其充当普通的超类型,不如说B是元,它描述了一个类型A应该具有一些引用A自身的属性。这不是传统 OOP 意义上的继承,而是更抽象的东西。(虽然它仍然是通过传统的继承机制实现的,但可以想象语言可以将它作为一个独立的概念来推广。)

Comparable是这个用例。它描述了某种类型可以与自己进行比较。由于它不是传统的 OOP 类型,理想情况下我们不应该使用静态类型声明对象Comparable。我们在公共方法返回/参数类型中看不到它,它没有多大意义。相反,我们会看到类似

<T extends Comparable<T>> 
void sort(List<T>)

这里的方法需要一个符合 Comparable 模式的类型。

(我真的不知道我在本节中在说什么)

于 2013-06-14T04:00:17.353 回答
2

举个例子:

E extends Comparable<E>

这意味着 E 必须是一个知道如何与自身进行比较的类型,因此是递归类型定义。

不知道它是否有任何正式名称,但我将其称为递归泛型类型模式

于 2013-06-14T00:11:27.497 回答
1

此模式与任何其他子类相同。使用泛型时真正发生的是 JVM 正在创建一个类的副本(实际上不是副本,但它有点像那样),并用指定的类型替换使用泛型的所有位置。

因此,要回答您的问题,该模式所做的就是替换B<A>其中B的所有用途A都被替换为任何类A。这样做的潜在用途是在您java.util.Collections为特定类自定义数据结构(来自 )的情况下,例如使用位移将 a 压缩Collection<Boolean>到更少量的内存中。我希望这是有道理的!

于 2013-06-14T00:12:50.190 回答