Ah right, I forgot the last step: Splitting each class according to concerns. And that’s indeed one step beyond a mere multiple inheritance hierarchy.
Actually I might be doing OO as a “split along concerns” thing by instinct already - see the “tear lines” in the ScreenObject class in http://hub.jmonkeyengine.org/forum/topic/monkey-blaster-toolforger-edition-p1/ . Each block between tear lines would be a candidate for componentization I think, though I’m not sure that I’d do such fine-grained components in the end. Splitting stuff is all fine and dandy, but it runs into too many Map lookups as soon as you have code that needs to touch more than one component.
(In the database analogy, people tend to clump unrelated concerns into the same table if they happen to share the same unique keys; you could split stuff up, but you’d be doing lots of joins.)
These tear lines I’m doing are an Eiffel influence, Eiffel has the concept of a “feature group”. An example is at EiffelWebNino/http_connection_handler.e at master · jvelilla/EiffelWebNino · GitHub , feature group headers have the form feature [export list] – comment
. Not all Eiffel feature groups indicate component boundaries in practice, but many do.
I’m not sure whether this radical split into components is really a good idea though.
The advantage is that it forces programmers to formalize aspects so you don’t have that huge bunch of ad-hoc classes containing a mixed jumple of aspects (which would become components); that’s in fact a good thing.
The disadvantage is that you lose all encapsulation. If a specific kind of entity has consistency conditions between different components, you’re back at conventions since there’s no way to enforce them anymore; you’d have to check all systems whether they’re upholding the invariants for each kind of entity. This doesn’t affect software that’s small, or where there are no component-spanning invariants in the first place. (Databases are bad at this, too - well, you could write a constraint that does a SELECT on another table, but I have never seen that being done, probably for fear or performance problems.)
I’m wondering whether it’s possible to avoid the disadvantages by turning the components into Java interfaces. E.g. class Ship implements PositionComponent, VelocityComponent, ModelTypeComponent, … more components here
.
I’m not saying that this is the best design yet… maybe no good design exists and an entity system is just a different trade-off that works better in some circumstance (though I’m still unsure how one would recognize such circumstances).
Let me outlne a use case for component-spanning rules:
There’s a PositionComponent and a VelocityComponent.
Footsteps cannot have any velocity other than zero. This can be handled by never creating a Velocity object for a Footstep object, but how do I make sure that the noob programmer next cubicle over didn’t accidentally create one? Or at least make sure that he’s the one getting the exceptions flying - where do I put the code that checks this, can I check this at all?
Now for units. They can’t move horizontally while underground. VelocityComponent’s constructor can check that, and if you go against advice and use setters, these can check that, too, so that’s covered.
Enter burrowers. These can move freely while underground. The check now would have to take into account whether the entity in question is a burrower or not, but the VeclocityComponent doesn’t know about entity types, so the check would have to move to a System (and I’d agree if you said that the check never belonged to a component class anyway). But now how to I make sure that no system ever creates a VelocityComponent that violates these rules? If I have multple kinds of burrowers (say, with different kinds of rules for burrowing in earth, lava, water), how do I code these checks without duplicating the checks all over the place?
Another interesting question: Which parts of the whole program know what componenties are tied into an entity - just those that create an entity? Then the program will have trouble with interacting components; you’ll have to encode entity type somewhere in the component so that the code even knows it has to do something special. (The encoding could be partial - e.g. the Velocity component might now have a VelocityRestrictions enum that detailed what kinds of velocities might be allowable. A more sophisticated system with parameterized restrictions might be set up with references to VelocityRule objects. It would be a lot of code to distinguish ground units, swimmers, burrowers, and flyers, and any System could still accidentally create invalid velocities.)