Search code examples
javagenericsclass-design

Restricting Containers with Generics


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:

  • there have to be "Ports" (Input and Output) which transfer Elements from one container to another. I'll have to throw some generics magic at these, too.
  • there's a GUI editor for the models on my todo list. So I'll have to make these checks available at runtime, too. So I guess there will be type literals and something like 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.
  • It would be cool if subclasses of 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:

  • Am I re-inventing the wheel? Which library does that for me?
  • Can and should I tackle this with generics? (if the answer is yes: some hints on the implementation are very welcome)
  • Could this be a bit over-engineered?
  • Are there a better title or tags for this question? :-)
  • (just drop your opinion here)

Solution

  • 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. :)