On Multiple Inheritance and Design Issues (controversy!)

Started by
20 comments, last by leiavoia 19 years, 10 months ago
A real simple way to avoid name collisions when using multiple inheritance:

1) put the name of the class in the member functions and variable names
2) avoid multiple inheritance of anything but abstract classes
3) avoid inheritance heirarchies bigger than two levels; use aggregation & composition instead

If you design to an interface when designing your base (abstract) classes with the idea that they''ll be multiply inherited, you can consciously avoid name collisions.

The OP''s idea is a good one; combine the facets of the applications'' game models as several different interfaces. Use Observers that observe each particular model type.

The concrete Model class you create from several base classes has the advantage of being able to mix data from each model type without breaking encapsulation.

On a pragmatic level, it makes sense that a real-life Object can be several different, unrelated things... why not multiply inherit?


Chad
my_life:          nop          jmp my_life
[ Keep track of your TDD cycle using "The Death Star" ] [ Verge Video Editor Support Forums ] [ Principles of Verg-o-nomics ] [ "t00t-orials" ]
Advertisement


As you can see, the ugly part is the whole collision detection scheme which forces diamond inheritance. It works, but is not ideal. I may get rid of half the diamond and find some other way of implementing that functionality. The problem is that the class structure is closely tied to my basic collision detection strategy which needs to be able to handle 1) multiple collision geometries and 2) multiple "types" like player, enemy, world, etc

However, all of this is the "setup". Everything in the game is a combination of these basic core classes. Once you get to the bottom of the list (where we find actually usable game objects), you don''t have to worry about all that jazz up top, it''s just a straight inheritance unless you want something more funky. For example, A Particle class may by a combonation of core classes, but if you want different kinds of particles, they just inherit from Particle instead of trying to do it from scratch.
Guideline
: Never inherit publicly to reuse (code in the base class)
: inherit publicly in order to be reused (by existing code that uses base objects polymorphically).

: Herb Sutter

[edited by - quorn on June 11, 2004 1:41:14 PM]
my suggestion would be to get rid of the whole collisionenemy/player/object interface. i can''t see that it''s necessary. you can get the same effects through collision IDs & messages. i belive you have that interface to handle things like enemy bullets hurt players, but not other enemies and stuff of that nature.

essentially what you can have is a collision matrix that defines collision types and with what other objects they collide

//  1    2    3    41   X2   0    03   X    0    X4   0    X    X    X 


since effects are symmetric you only need to define one half of the matrix. but essentially X indicates "collides with" and 0 indicates "does not collide". So a bullet, when it collides, always does the same thing -> send a "Hit_by_bullet" message to the collidee. you then have each collidee implement a "Hit_by_bullet" message handler that applies damage. becase enemy bullets are transparent to enemies, they will never receive the "Hit_by_bullet" message except from a player bullet so you don''t need to bother with specific logic like

if bullet from player && i''m an enemy
....
else
....

this also offers a nice easily expandible situation. someday you might decide it''d be nice to have friendly fire. in that case you just change a 0 to an X rather than having to implement a new method in your enemyCollisionHandler routine.

as far as straight up collision detection it shouldn''t be different for different objects outside of some parameters that should be abstracted to the collisionObject base class (elasticity, mass, velocity, collision volume shape, etc). the physics of collision is straight forward and can be nicely handled by a single base class & physics system. the physics system should obviously reference the collision matrix and ignore collisions between objects with a 0 in their matrix field. outside of that, you just have the physics system call some virtual method that you will extend to pump out your collision messages.

anyway, this is perhaps offtopic, but i''m trying to go for the "is this bad" aspect in a more concrete way.

-me
Yes i understand the getting rid of the player/enemy/world collision objects. That's my next project in fact.

The collision matrix is actually what i have now. But i actually have 2 matrices: one for geometry, one for object type. I do this because i don't even want to check collision between objects for which it makes no sens. Bullets ought not to collide with other bullets. So i don't define any function for that. (the matrix is a table of function pointers). If there is a function for it, i use it. The function then calls a virtual function in each object, like so (cut and paste example):

void IntersectTestManager::IntersectPlayerWorldWrapper( CollisionObject* A, CollisionObject* B ) {	CollisionObjectPlayer* objA = dynamic_cast<CollisionObjectPlayer*>(A);	CollisionObjectWorld* objB = dynamic_cast<CollisionObjectWorld*>(B);	if (objA) { objA->IntersectWorld(objB); }	if (objB) { objB->IntersectPlayer(objA); }	}


The nice thing about this system is that i can inherit the collision functionality but since it's all through virtual functions, i can define it in a base class and reimplement it downstream if i want.

For a good read on how i came up with this system and a lot of source code, check out this thread

[edited by - leiavoia on June 11, 2004 2:00:03 PM]
If inheritance was not for code reuse and only for typing, then inheritence would not include all the functional code that you included in your parent class, only the function headers. Instead of "extends" there would only be the java style concept of "interfaces" that are "implemented".

Ineritence != polymorphism. Both are good features that are useful for different things. Inheritence is a time savor and readability aid, wheras polymorphism is the powerful functional tool that the original quote refers to. Both must be used carefully.

This is also why I think bashing multiple inheritence is a crock. If you use multiple inheritence purely for polymorphic purposes (instead of for code-reuse purposes) it runs very neatly. Only if you use it for code-reuse do you start getting into trouble, which is I think the point of this complaint. In other words:

"All broad, sweeping generalisations are false."
-- Single player is masturbation.
Nobody stated that inheritence shouldn''t be done for code reuse - we just stated that it''s not it''s PRIMARY purpose; polymorphism is.

Without polymorphism, there is no point in using a class-based heirarchy in the first place, as it has no tangible advantage over a functional paradigm.

---------------------------Hello, and Welcome to some arbitrary temporal location in the space-time continuum.

quote:Original post by Pxtl
If inheritance was not for code reuse and only for typing, then inheritence would not include all the functional code that you included in your parent class, only the function headers.
It doesn''t. Only virtual functions and protected members are inherited, which would seem to suggest that it''s an escape to provide some reuse as opposed to a mechanism intended fundamentally for effecting code reuse.

quote:This is also why I think bashing multiple inheritence is a crock. If you use multiple inheritence purely for polymorphic purposes (instead of for code-reuse purposes) it runs very neatly.
Which is exactly the point of the MI-bashers, and the argument in favor of interfaces.

Disestablishmentarianism. So sad, so tragic.
I don''t even really like *single* inheritance, let alone multiple inheritance. I tend to be more comfortable with object composition, and I wish there were an Algol-syntaxed language which could do more of the delegation for you automatically (I hate the wrapper functions too). It''s one of the things I have in mind for Defn.

Anyway, MI can be a useful tool, but it''s quite unnecessary (in the way that basically everything else in a HLL is unnecessary). I''m personally quite used to the Java way of doing things (where you implement multiple interfaces, and supposedly miss out on the code reuse; but you can always use composition as a "back-end" for implementing those interfaces). Inheritance and delegation are to a large extent equivalent, and I''m more comfortable thinking of things in terms of delegation, generally speaking. YMMV.

That said:

quote:Original post by leiavoia
I have a "player". A player is a
- UpdateObject (used to call object->Update() every frame)
- CollisionObject (of course)
- LifeObject (has hit points and can die)
- PhysicalObject (has a location, speed, weight, and vector)

So what i do is program each of these classes seperately. Then i patch them all together to create a "Player" class like so:

class Player: public UpdateObject, public CollisionObject, public LifeObject, public PhysicalObject {// override functions and whatnot}    



I understand that a physical object may or may not be able to collide with things, but something which collides needs physical properties. I''d probably end up with something like:

class Object { /* anything that can ->Update(). */ }class LivingObject : public Object {  /* if it has hit points, shouldn''t it necessarily update each frame? */}class PhysicalRepresentation {  /* a set of measurements for an object */}class CollidablePhysicalRepresentation : public PhysicalRepresentation {  /* adds stuff needed for collision detection */}class Player : public LivingObject {  CollidablePhysicalRepresenation cpr; /* hee hee */}


You can see perhaps how that will extend to your full structure. Although I think you will have to end up implementing some kind of multiple dispatch for collision :/
quote:Original post by Zahlman
I'm personally quite used to the Java way of doing things (where you implement multiple interfaces, and supposedly miss out on the code reuse


Thats not true, you can utilise interface & implementation inheritance together, you get a clean abstraction & code reuse.

This is done with java inteface that declares a type, an abstract class implements the type/interface and provides an extension point of code reuse by making concreate classes derive from it, then you use/store interface pointers for dynamic polymorphism. It is done alot in the java libraries.

Take a look the collections library for alot examples. List is a interface type, they provide an abstract class called AbstractList that implements the interfaces provides an extension point for code reuse and that is without module coupling that people keep doing by making there data members protected.

[edited by - snk_kid on June 11, 2004 8:09:53 PM]

This topic is closed to new replies.

Advertisement