Let’s assume there is an ArrayList that contains many different elements of type “Element”. All of them will be drawn to the screen in an endless loop.
These “Element”s can implement 0..n interfaces like “Movable”, “Selectable” and so on.
What I tried so far is iterating through all Elements and check for an interface like:
ArrayList<Element> allElements;
...
for (Element element : allElements) {
if (element instanceof Movable) {
((Movable)element).move();
}
if (element instanceof Selectable) {
...
}
element.draw();
}
I’m not happy with that approach, though, because it violates the open/closed principle (and probably thousands of other principles too). Of course, I could re-design it so that each element responds itself according to the interfaces it implements:
for (Element element : allElements) {
element.move(); // element checks itself if it can move, and if true moves
...
}
The drawback is that the Element class must provide signatures for each possible behavior, i.e. it must provide the methods of every interface and maybe override them in child classes. That’s also not what I want because it bloats the Element class.
I also tried selecting Elements by interface like this:
getElementsByInterface(Movable, allElements) { ... }
(Movable is the interface, allElements the ArrayList)
But it doesn’t compile. It seems Java doesn’t know interfaces at run-time anymore.
Long story short (sorry for my possibly overly verbose post):
What is the best design solution to let array elements react according to their abilities(/interfaces)?
I see nothing inherently wrong with this approach.
There are basically two approaches to this scenario:
Use polymorphism and the Visitor pattern. Due to (the interface aspect of) the LSP, this introduces the “drawback” described of each handler having to implement all actions.
Using local type-based switching (this is more common place in languages like Scala with ADTs where this approach is acceptable/common; the Java syntax is just a bit unwieldy in comparssion).
There is no violation of (polymorphic) open/closed here because interfaces are being used and the “action” is still left in the implementation (which still supports polymorphism and thus “open”); it is only the selection of action that is not.