I'm currently in the process of designing the class hierarchy for a simulation project. It is going to be a discrete-event simulation of a tree of Element
instances. For brevity (and because I'm only interested in the generics part of the problem) I'll only present sketches of the classes / interfaces here, so the syntax will not be perfect. To have something to start with, an element might look like this:
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); }
}
The definition of Position
does not really matter, and what the other methods are supposed to do is self-explanatory I hope. To make things mildly interesting we throw another interface into the pool:
public abstract class Solid extends Element {
public Size getSize();
}
Again, the exact meaning of Size
does not matter, a Solid
is just supposed to be a physical object which fills space in the simulated world. Now that we have solid object we may want to stack them on top of each other, so here's a Stack
:
public abstract class Stack extends Solid {
public void add(Solid s); // <- trouble ahead!
}
And here's where the trouble starts. I really want only instances of Solid
to be added to the Stack
. That's because it's difficult to stack objects that have no size, so the Stack
will need something which has a Size
. Therefore I rework my Element
to allow expressing this:
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);
}
My intention here is to express that an Element
can have a restriction on which (sub-)types of Element
it may contain. So far this works and all. But right now I felt the need to have an restriction on the container of an Element
. What does that look like?
public interface Element<E extends Element<?,?>, C extends Element<?, ?>>
Or does it go more like this:
public interface Element<E extends Element<?,C>, C extends Element<E, ?>>
I'm starting to feel a bit fuzzy about this, and there's more to the game:
Input
and Output
) which transfer Elements from one container to another. I'll have to throw some generics magic at these, too.public boolean canAdd(Element<?, ?> e)
the editor will have to rely on. Because this method is so important I'd like to have it an final
method of the Element
class, so no drunken developer can get it wrong at 4am.Element
could decide for themselfs which Collection
class they are using, because from some of the a LinkedList
or whatever seems a perfect fit.So here are my questions:
I'm still a bit fuzzy as to what you're trying to do, but if you want to run with this all the way to its logical conclusion, I'm seeing something like this:
/**
@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();
}
That expresses (I think) all your constraints: my parent must be something that can have me as a child, and my children must be something that can have me as a parent.
Implementations would include:
/** 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> {…}
I don't have a Java IDE open right now, though, so I can't confirm this will actually compile -- I have a feeling that the compiler will complain about those question marks, and try to drag you into something like
class Agglomeration extends Element<?
extends Element<?
extends Element<?
extends...
You can get around that, though, if you separate Parent
and Child
into separate interfaces (one with getChildren()
and one with getParent()
), so Agglomeration
only implements the first and Solid
only implements the second, while Stack
implements both.
It might be more trouble than it's worth, but run with it for a while and see where it takes you. :)