使用泛型限制容器

时间:2011-01-19 20:55:13

标签: java generics class-design

我目前正在为模拟项目设计类层次结构。它将是Element个实例树的离散事件模拟。为了简洁(因为我只对问题的泛型部分感兴趣),我只会在这里提供类/接口的草图,因此语法不会很完美。要开始使用某个元素,元素可能如下所示:

public abstract class Element {
    private Set<Element> children = new HashSet<Element>();
    public Element getContainer();
    public Collection<Element> getChildren() {
       return Collections.unmodifiableSet(children);
    }
    public Position getPosition();
    protected void add(Element e) { children.add(e); }
    protected void remove(Element e) {children.remove(e); }
}

Position的定义并不重要,其他方法应该做的是我希望不言自明。为了使事情变得有趣,我们将另一个接口放入池中:

public abstract class Solid extends Element {
    public Size getSize();
}

同样,Size的确切含义并不重要,Solid应该是一个填充模拟世界空间的物理对象。现在我们有了实体对象,我们可能希望将它们叠加在一起,所以这里是Stack

public abstract class Stack extends Solid {
    public void add(Solid s); // <- trouble ahead!
}

这就是麻烦开始的地方。我真的只想将Solid的实例添加到Stack。那是因为堆叠没有大小的对象很困难,因此Stack需要具有Size的东西。因此,我重新编写Element以允许表达:

public abstract class Element<E extends Element<?>> {
    private final Set<E> children = new HashSet<E>();
    public Element<?> getContainer();
    public Collection<E> getChildren();
    public Position getPosition();
    public void add(E e);
    public void remove(E e);
}

我的意图是表达Element可以限制它可能包含哪些(子)类型的Element。到目前为止,这是有效的。但是现在我觉得需要限制Element容器。那是什么样的?

public interface Element<E extends Element<?,?>, C extends Element<?, ?>>

或者更像是这样:

public interface Element<E extends Element<?,C>, C extends Element<E, ?>>

我开始对此感到有些模糊,而且游戏还有更多内容:

  • 必须有“端口”(InputOutput),它们将元素从一个容器转移到另一个容器。我也不得不在这些方面投入一些仿制药。
  • 我的待办事项列表上有模型的GUI编辑器。所以我也必须在运行时提供这些检查。所以我想会有类型文字和类似public boolean canAdd(Element<?, ?> e)的编辑器必须依赖的东西。因为这个方法非常重要,所以我希望它具有final类的Element方法,所以没有醉酒的开发人员可以在凌晨4点弄错。
  • 如果Element的子类可以决定他们正在使用的Collection类,那会很酷,因为从LinkedList中的一些或者看起来非常合适的任何东西。

所以这是我的问题:

  • 我是否重新发明了轮子?哪个库能为我做到这一点?
  • 可以而且我应该用泛型来解决这个问题吗? (如果答案是肯定的:非常欢迎对实施的一些提示)
  • 这可能有点过度设计吗?
  • 此问题是否有更好的标题或标签? : - )
  • (只是放弃你的意见)

4 个答案:

答案 0 :(得分:1)

只是一些随机的评论:

而不是Collection<E>您可能会或可能不想Collection<? extends E>

我很确定,你想从Element<E extends Element<**E**>开始,因为你想捕获它的类型(只看java.lang.Enum)。所以你需要像Element<E extends Element<E,C>, C extends Element<C, ?>>这样的东西 对于你和编译器来说,它可能会变得太复杂(也许它会变得更好,但前段时间我在尝试太多时遇到了一些非常奇怪的错误)。

也许方法Element.add和Stack.add实际上是不同的方法?将子项添加到Stack意味着将它们堆叠起来,将它们添加到任意元素可能意味着完全不同的东西?

  

如果Element的子类可以自己决定使用哪个Collection类,那将是很酷的,因为从一些LinkedList或其他看起来非常合适。

在基类的ctor中调用Collection<C> makeCollection()之类的方法可以做到。但是,大多数时候使用LinkedList是错误的(它的空间局部空间不好,空间开销很大,因此它的优点可能会被忽略)。

  

所以我也必须在运行时提供这些检查。

因此,您需要使用Class<E>参数构建实例。

  

返回Collections.unmodifiableSet(children);

通过这种方式,您可以返回儿童的实时视图。请考虑使用com.google.common.collect.ImmutableSet.copyOf(children)。

答案 1 :(得分:1)

对于你想要做的事情,我仍然有点模糊,但是如果你想一直运行到它的逻辑结论,我会看到这样的事情:

/**
@param P parent
@param C child
@param S self
*/
interface Element<P extends Element<?, S, P>, 
        C extends Element<S, ?, C> ,
        S extends Element<P, C, S>> {
    public P getParent();
    public Collection<C> getChildren();
}

表达(我认为)你所有的约束:我的父母必须是可以让我作为一个孩子的东西,而我的孩子必须是可以让我作为父母的东西。

实施包括:

/** An agglomeration of stacks: no parent */
class Agglomeration extends Element<?, Stack, Agglomeration> {…}

/** A stack of solids */
class Stack extends Element<Agglomeration, Solid, Stack> {…}

/** A solid in a stack: no children */
class Solid extends Element<Stack, ?, Solid> {…}

我现在没有打开Java IDE,所以我无法确认这实际上是否会编译 - 我有一种感觉,编译器会抱怨这些问号,并试图将你拖入某事像

class Agglomeration extends Element<? 
    extends Element<? 
        extends Element<? 
            extends...

但是,如果您将ParentChild分隔为单独的界面(一个getChildren()和一个getParent()),您可以解决这个问题,所以{{1只实现第一个而Agglomeration只实现第二个,而Solid实现它们。

它可能比它的价值更麻烦,但与它一起运行一段时间,看看它需要你。 :)

答案 2 :(得分:0)

我的答案基于我对你的设计的理解。如果我理解错了,请告诉我!

泛型的主要目的是确保类型安全而不是对象关系(据我所知,无论如何)。看起来你可以通过确保类型安全来增强对象之间的关系,但最终的目的是确保类型安全(这样你就不会坚持不属于它的东西)。

在您的抽象类中,您提供了一个名为add的方法。这意味着扩展此抽象类的任何内容都必须实现add。现在稍后,您对add施加限制,表示您只能add Solid个对象(Element的子类)。但是在这样做时,你违反了抽象类的契约,它表示可以将Element(或其子类)添加到集合中。

所以有两种选择。您要么必须重新考虑您的设计(可能不应在抽象类中提供add?)。当您尝试UnsupportedOperationException没有大小的对象时,您的另一个选择是抛出运行时异常(可能是add),并提供一个检查方法(如canAdd)开发人员知道是否可以添加特定对象。

答案 3 :(得分:0)

看看这一行:

public interface Element<E extends Element<?,?>, C extends Element<?, ?>>

让我觉得是的,这肯定是过度设计的。

当然有很多方法可以解决这个问题,但听起来适合我的方法是使用Decorator模式而不是继承(不是在任何地方,只有在您认为原始设计正在磨损的地方)并且可能通过这种方式,仿制药也能更好地适应。