The Multiple Inheritance Monster

Started by
6 comments, last by jpetrie 15 years, 8 months ago
I'm looking for some "Best Practices" -type explanations here. This specific example applies to games (because this is Gamedev, derr) but really speaks to any instance of multiple inheritance being deployed across a class architecture. Let's take an RPG. You have many items, monsters, NPCs, .... many. All of these are obviously represented as objects. So let's say we have classes Item and Monster (and like 20 more):

class Item
{
    int value;
    double whatever;
    // ...
}

class Monster
{
    int hp;
    int thaco;
    int ac;
    // ...
}

Now I would like to introduce the concept of Magic. *Anything* and everything that is "magical" has several data fields and methods, namely: int mp; int maxMP; LinkedList<Spell> spells; bool cast(Spell*) ... Managed languages such as Java and C# prohibit multiple inheritance, referencing "increased complexity" and the "diamond problem" as reasons for doing away with it. Other languages such as C++ allow it. I see a number of avenues possible here (and the avenue I choose of course is language-dependen). However I would like to hear what anybody has to say about these proposed alternatives (and of course whatever you can contribute as well). (1) Pure Multiple Inheritance. class Item : GameObj, Magic Items are thus extensions of type GameObj and the Magic class. (2) Magic Interface interface IMagic { bool cast(Spell*); /* etc... */ } This enforces a contract for every implementing class, so we might override for magic items and magic monsters separately, maybe like this:

class Monster : Magic
{
    int hp;
    int thaco;
    int ac;

    bool cast(Spell* splz)
    {
        // whatever
    }
}

However there are now two problems: (1) we don't enforce mp and maxMP data fields in any magic objects, and (2) we now require every monster to be magical, which we don't want, because as we all know, not every monster is magical. (3) Virtual Methods We could make GameObj magical and give it virtual functions:

class GameObj
{
    // ... whatever

    public virtual bool cast(Spell*);
}

And now we can choose to override it in certain classes, or not in others. Thus we could have an Item class which inherits GameObj and does not override (for non magical items) and a MagicItems class which also inherits GameObj and does override (for magic items). The problems here are numerous : Item and MagicItem are different types which make them incompatible for collections and other mechanism (whereas they should be viewed as the same, essentially). Also its getting complex and wreaks of spaghetti. So basically, from a Best Practices, standpoint, what is the best choice to run with here (pay no attention to language choice, I'm really looking for good engineering advice)? And why?
Advertisement
Quote:
So basically, from a Best Practices, standpoint, what is the best choice to run with here (pay no attention to language choice, I'm really looking for good engineering advice)? And why?

Consider composition instead of inheritance, e.g., give each object that should contain 'magical properties' a MagicalProperties object (choosing better names, of course) instead of trying to force it in there via inheritance, which leads to clunky MI problems you've run into.

Game development is starting to edge away from the deep inheritance based models that have been previously in vogue towards more component-based designs that the rest of the software development world started exploring ages ago. It's some times hard for beginners to see how this actually improves the object-oriented design of their interfaces, having typically been indoctrinated to tightly associate "OO" and "classes, inheritance, polymorphism." But this isn't actually the case; the deep and MI heavy approach tends to actually violate more fundamental OO concepts, such as some of those discussed in the ObjectMentor articles linked here (you may need to toggle over to the second page of results).

So in summary, I'd avoid both your options: the MI one doesn't scale nicely, and the option where you push functionality up into the base is poor design because it hoists potentially "dead" functionality in the base interface. I'd opt for an aggregation approach here.
To paraphrase what jpetrie is saying: In well-written object-oriented code, it's rare to see classes inheriting from other actual classes. Inheriting from interfaces (that is, classes with nothing but pure virtual functions in them) is more common, but inheriting something that has member variables is rare. clicky
So one of the problems you have is that you can't keep track of Item and MagicalItem objects in the same collection, right? Well why not create a base class Item, then have MagicalItem derive from Item? Then you have two types of Item objects -- this is where single inheritance makes sense to me.

Everything derives from GameObject, right? Next you might have a Creature class, that both your Monster AND Hero class could derive from. Going another route, you have a basic Item class, then a special MagicalItem class that derives from Item.

So you end up with a hierarchy like that that is fully compatible in reverse:

GameObject
|--Item
|--MagicalItem
|--Creature
|--Hero
|--Monster

Now you can have a collection of Items that include MagicalItems (because the Item class is it's base class). Programming languages like Java and C# have single inheritance for full classes to avoid the diamond effect or the problem where you have two super classes with the same method or field, but they allow multiple inheritance from interfaces (classes that must be overridden to define them and contain no fields). But if you don't have to use multiple inheritance, which in most cases you don't, then DON'T use it.

Now you want both Items and Monsters to be magical (and likely Heroes), but they don't necessarily need to inherit Magic, rather they can HAVE Magic. So make it a property, or put it in an interface that both MagicalItems and Monsters can inherit from regardless of their parent.

That's my two cents...from an engineering perspective.
Consider the possibility that what you're trying to model isn't a good fit for a static type system. Data driven entities are often better implemented in a dynamically typed environment like Python. You could embed a scripting language in your application to handle the data end of things.
If you are using Mangaged language, Use composition( Containership OR Has-a relationship). This aproach will do each single thing you will need, except It will be a little tedious compared to Multiple Inheritence(Eg. calling the parent's functions using child ptr.)
General practice is to implement Abstract class for inheriting. So that u can get full benefit of the polymorphism.
Quote:
If you are using Mangaged language, Use composition( Containership OR Has-a relationship). This aproach will do each single thing you will need, except It will be a little tedious compared to Multiple Inheritence(Eg. calling the parent's functions using child ptr.)

Managed languages have nothing to do with it. The technique is equally suited to managed and unmanaged languages.

Quote:
General practice is to implement Abstract class for inheriting. So that u can get full benefit of the polymorphism.

Whether or not a class is abstract has little bearing on whether one gets 'the full benefit of the polymorphism' from it. I'm not even sure what you mean by that.

This topic is closed to new replies.

Advertisement