5

Sorry for this ugly question, but I didn't know how to word it. I'll give an example of what I mean:

A Human can be a Mage or a Warrior, so Mage and Warrior could inherit from Human. But what if Orc can be a both too? We can't say "a Human is a Warrior" or "a Warrior is a Human". Do Orc and Human (or a parent class, Humanoid) inherit all the skills, and then choose what to use?

I don't know if I should tag a specific language, since it's a general question about oop, but since different languages can have different approaches to the same problem, I would prefer answers from a C++ perspective.

Ciscool
  • 95
  • 1
  • 6
  • 4
    It's a somewhat controversial topic, but c++ *does* support multiple inheritance: http://www.cprogramming.com/tutorial/multiple_inheritance.html - using multiple inheritance, a Human can be a Warrior or a Mage, and an Orc could also be a Warrior or a Mage. Not that this is necessarily recommended. – neminem Jun 23 '14 at 22:39
  • @neminem But is it ok to inherit from a bunch of classes if you will only be using one of them for every child object? – Ciscool Jun 23 '14 at 22:41
  • "Ok" is up to you. It's certainly *allowed* - in this case it'd probably be *cleaner* for your "Humanoid" class to have "Class" and "Race" properties, and then have the classes and races inherit from their respective base classes, rather than having your Humanoid inherit directly from classes and races. But you *could* do that. (There have been times occasionally when I really wish C# did support multiple inheritance...) – neminem Jun 23 '14 at 22:43
  • 2
    The problem with multiple inheritance here is that you'd need to have N^2 _final_ classes so there would be a class for each combination of race and class (e.g. `class HumanMage: public Human, Mage {...};`). This can quickly get out of hand. – Mike DeSimone Jun 23 '14 at 22:50

6 Answers6

8

Improve your modelling

  1. Abstract class Race, concrete classes Human, Orc, etc...
  2. Abstract class Class, concrete classes Mage, Warrior, etc...

When calculating the stats for your Mage, for instance, ask Race (not Human, or Orc) for things like int get_intelligence_bonus() (abstract function in Race). If at all possible, interactions should be between Race and Class, not the concrete counterparts.

Use composition to actually construct your character:

Character::Character( std::unique_ptr<Race> r, std::unique_ptr<Class> c ) 
{
  // calculate stats, etc...
}

You can use pure OOP for dynamic binding or if you prefer, you can use templates for static-time binding, but that's an orthogonal implementation issue. Either way, you need to get your class hierarchy right.

If you are new to C++ or if you only need race and class in the constructor, you may use const& instead of std::unique_ptr<>. But if you are serious about learning C++, do read about ownership semantics and variable lifetimes so you can better understand std::unique_ptr<>.

http://en.cppreference.com/w/cpp/memory/unique_ptr

kfmfe04
  • 14,936
  • 14
  • 74
  • 140
  • So the character would get its stats at the moment of the creation without actually being a `Race` or a `Class`? I like it, but the template thingy is too advanced for me :) Also, I should read about `unique_ptr`, but I assume it points to a class not meant to create objects? – Ciscool Jun 23 '14 at 23:14
  • 1
    That is correct: prefer composition over inheritance. An alternative to using `std::unique_ptr<>` is to use `const&`, but then you may need to to **clone** your Race or Class instance or hold a reference to it. Instead, `std::unique_ptr<>` allows your Character instance to **own** the memory passed in. This is better encapsulation and makes more sense: when your Character instance disappears, so will the Race and Class instances. I am assuming that you will need to use those instances later. If you only need them in the constructor, then a `const&` passed in is fine. – kfmfe04 Jun 23 '14 at 23:18
  • 1
    http://en.cppreference.com/w/cpp/memory/unique_ptr Recall what happens to automatic (local) variables: when they go out of scope, the destructor is called and memory is reclaimed. `std::unique_ptr<>` behaves like this - when it falls out of scope, the destructor will be called and memory is reclaimed. – kfmfe04 Jun 23 '14 at 23:27
5

That's when I break out policy-based design. Then I can have Human<Warrior> or Orc<Warrior> or Orc<Mage>. (Or turn it around if it makes more sense: Warrior<Human> etc.) But that's if I know what's going on at compile time.

If I don't know until run time, I'd use a class Humanoid which has a Race (subclasses would be Orc, Human, etc.) and has a RPGClass (subclasses Mage, Warrior, etc.)

In other languages, there are things such as protocols which can define the interface to an object, so that you don't have to define a base class full of abstract virtual methods and such. So RPGClass and Race would be protocols used to interface to classes such as Mage and Human respectively. Policies are just protocols resolved at compile time.

P.S. I have no idea how metaphorical (or not) your examples are...

Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
  • Yes, that's the major problem. I can't know before run time who will be what. They could even change multiple times. I guess composition is the way to go? And yes, it was a little vague my question, but I thought a more specific question wouldn't help much. – Ciscool Jun 23 '14 at 22:56
4

In this example, I would see a use for either composition or multiple inheritance.

That is, I would have a mage and warrior class as well as a human and orc class. If I want an Orc Mage, I inherit from both the mage and orc classes.

That said, in practice, multiple inheritance can be screwy (see here: What is the exact problem with multiple inheritance?). As a result, the orc mage class in my implementation would probably have an orc and mage private objects that would do the the orc and mage specific stuff.

Community
  • 1
  • 1
It'sPete
  • 5,083
  • 8
  • 39
  • 72
3

A being has an occupation (class) and race. There is no need for 'is-a' here.

There will be class-instance pattern uses, but they will probably not line up perfectly with C++ (or any other language) inheritance rules, so run it manually, restricting it or enhancing it as use cases appear, and be willing to throw it out if you hit a tangle.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • But the warrior can _be_ an Orc or can _be_ a human, and then e.g. be in a vector of Orcs, or humans. I like the idea of inheriting from two base classes (race and occupation) which both have concrete implementations (I think Jonathan and kfm suggested something like that). – Peter - Reinstate Monica Jun 23 '14 at 23:00
2

If I follow, you are trying to define a class Warrior, that can be Human or Orc, but should inherit of the class in question.

If that is what you want to do, the correct way to go about it is to write a generic Warrior class:

template<typename Race>
class Warrior : public Race
{
    // Warrior attributes & methods
};

By doing this, you will be able to instanciate human warriors (Warrior<Human>) as well as orc warriors (Warrior<Orc>), or warriors from any other race.

However, Warrior<Human> and Warrior<Orc> will be considered as totally different classes, which do not belong to the same inheritance tree.


Now, maybe that is not what you want to do. Maybe you want to be able to manipulate containers like vector<Warrior*> to handle both human and orc warriors indicriminately and make use of polymorphism. In this case, it may make sense to reverse the inheritance and to have a template class Human<Class> that inherits from Class.

But what if you want to also be able to manipulate containers like vector<Human*>? Well, in this case, you want to handle characters polymorphically with regard to their race or class, so it should inherit from both!

template<typename Race, typename Class>
class Character : public Race, public Class
{
    // Character attributes & methods
};

Then you can safely have an inheritance tree for character classes and one for races.


Then again, if you want to be able to manipulate things like vector<Character*> to handle all characters at once, that won't do. The simplest thing would be to create a class AbstractCharacter, from which all characters would inherit. But what if, while you're at it, you want your characters to be able to change class? Then you have to change your design: maybe your character is not a human that is also a warrior, but a character that has a race that is 'human' and a class that is 'warrior'.

In this case, make race and class attributes of your character:

class Character
{
    Race* characterRace;
    Class* characterClass;

    // Character attributes & methods
};

where Race and Class are the roots of the inheritance trees of races and classes respectively. And you will still be able to use polymorphism.

(Note: although I wrote it like this for more readibility, it would be better to use smart pointers instead of regular pointers in the actual implementation)

Edit: when I talk about changing the class of the character, I mean at runtime. And that also means that you can create a character of the class and race you want at runtime rather simply.

Eternal
  • 2,648
  • 2
  • 15
  • 21
1

Traditional OOP terminology includes "is a" and "has a". But no "can be".

You've provided very little details about your specific coding needs. But if I needed something like this, I would still use inheritance. I would create a base class that shares the common qualities of both possible items. And then I would declare specialized derived classes for each of the possible specific cases.

Then I would create the appropriate data type for each specific cases, that derive from my common case. I would derive the specific type depending on that type the item is. I could then create a collection of the base class that could contain different specific types.

If there is no common base class, I might use an interface instead of a base class.

In the end, it's impossible to provide specific examples when you've provided no specific examples of what you are trying to accomplish.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466