Basically, what I would like to obtain is a way to iterate through ONE list, and call methods specific to the interface the objects in the collection implement.
In my Java project, it would result in something like this:
//GameComponent would be my empty abstract class
HashSet<GameComponent> components = new HashSet<GameComponent>();
components.add(/* instance of a class that extends GameComponent and implements Drawable || Updatable */);
for (Drawable d : components)
d.draw();
for (Updatable u : components)
u.update();
My friends suggested me to just make two separate lists, but that means some objects will be in both lists.
I also think that it makes sense (abstraction-wise) to have one list of GameComponents and iterate through that one list.
But I also don't like having an empty class to inherit from, only for categorisation.
So, the question is: does my approach make sense? If so, is it elegant? If not, what pattern could I follow?
Best Answer
A couple of thoughts:
It's better if game components don't know how to draw themselves. A class should have only one reason to change; here your components may change if you need to change what they do in-game, and they could change if you need to change how they're drawn as well.
For this scenario the most straightforward solution is use
instanceof
. It's not the most general solution but it's the simplest thing that'll solve this particular problem.If you have a finite set of GameComponent types, what you want is a sum type, which mainstream OOP languages don't provide native support for but you can kind of emulate through the Visitor Pattern. (There's an alternative way of implementing them but it won't be elegant in Java; it works best with named arguments and lambda expressions.) This is a more general solution to taking decisions based on the particular kind of GameComponent because it gives you full access to the component type.
It would look something like this:
Now you can take component-specific decisions like this:
This works because any component needs to implement
accept
, and forvisit(this)
to compile theVisitor
needs to have a method that accepts that particular kind of component. It's still possible to implement a component such thataccept
doesn't callvisit(this)
, so we safeguard against that by using a private constructor. That prevents outside subclassing while allowing subclassing of inner classes (because inner classes have access to private members of their enclosing class.) The end result is a finite set of components and an exhaustive way to discriminate between them (you can't forget to handle one of them because the Visitor implementation wouldn't compile.)