我目前正在为模拟项目设计类层次结构。它将是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, ?>>
我开始对此感到有些模糊,而且游戏还有更多内容:
Input
和Output
),它们将元素从一个容器转移到另一个容器。我也不得不在这些方面投入一些仿制药。public boolean canAdd(Element<?, ?> e)
的编辑器必须依赖的东西。因为这个方法非常重要,所以我希望它具有final
类的Element
方法,所以没有醉酒的开发人员可以在凌晨4点弄错。Element
的子类可以决定他们正在使用的Collection
类,那会很酷,因为从LinkedList
中的一些或者看起来非常合适的任何东西。所以这是我的问题:
答案 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...
但是,如果您将Parent
和Child
分隔为单独的界面(一个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模式而不是继承(不是在任何地方,只有在您认为原始设计正在磨损的地方)并且可能通过这种方式,仿制药也能更好地适应。