6

I may look like a total idiot with such a question but nothing came up when I searched it, so I guess why not. Here is my problem:

I'm doing the UML work for a C++ game, that will feature animals and races.
First, I have one abstract class Entity, and three inherited classes Human, Dog, and Cat.
Now let's say I want to add a new sort of species, the Droids for example, in a way that there are three new classes: DroidHuman, DroidDog and DroidCat. That's three new classes to create for one new race, and if I want to add the people of the Void, that will be three more classes to create.

Instead of doing this, I'd rather have some kind of design pattern that allows me to stick with the three original classes and only these: Human, Dog and Cat.
A normal dog and a droid dog would both be instances of the Dog class that inherits from Entity. There would also be a Normal super class and a Droid super class. The difference between a normal dog and a droid dog would be that they inherit from EITHER Normal OR Droid.

I know I'm kind of asking the impossible but I guessed there would be a similar way to do this that I might not be aware of.
I first thought of the factory pattern, but I can't think of any way it would fix my problem.
The only other way I found to avoid this huge mess, is to add an attribute like boolean isDroid = true; for each new race. But I am not pleased with modifying existing code to fit new stuff.

I can't be the only one to have faced that problem, am I?
Any help is appreciated. Many thanks.

4

I would probably use the strategy pattern for the Normal vs. Droid classes. In C++ this is typically implemented as a template parameter:

class Normal {};

class Droid {};

template <class T>
class Entity {
    virtual void ~Entity() {}
};

template <class T>
class Human : public Entity {
};

template <class T>
class Dog : public Entity {
};

template <class T>
class Cat : public Entity {
};

Now you can instantiate a (Cat|Dog|Human)<Droid | Normal>. Essentially you have a rectangular matrix, where you have to write one class for each row and another for each column, and you can instantiate one over the other to produce a new class for the intersection of each row with each column.

You write M classes + N templates, and get M*N possible instantiations.

  • This is exactly the kind of syntax I was looking for. I will dig as well as the above answer. Thank you! – qreon Aug 17 '16 at 12:50
  • Are you sure Entity should be a template too? – Deduplicator Aug 21 '16 at 8:00
5

I would use the decorator pattern to avoid the classes' exponentiation

class Entity
{
    public:
        virtual void walk() = 0;
};

class Human: public Entity
{
    public:
        void walk()
        {
            //one human step
        }
};

class Dog: public Entity
{
    public:
        void walk()
        {
            //one dog step
        }
};

class Droid: public Entity
{
    private:
        Entity* origin;

    public:
        Droid(Entity* origin)
        {
            this->origin = origin;
        }

        void walk()
        {
            //initialize engine in order to be able to move

            origin->walk(); //walk as a normal one
        }
};

Usage

Entity* human = new Human();
Entity* dog = new Dog();
Entity* humanDroid = new Droid(human);
Entity* dogDroid = new Droid(dog);
  • 1
    Thank you I will dig that. Isn't it weird to say that the Droid contains a Human and thus he is a Human Droid? – qreon Aug 17 '16 at 12:48
  • 2
    @qreon I would rather explain that as "a droid with human capabilities and appearance". – Spotted Aug 17 '16 at 13:05
1

If you want to keep the same classes for droids and normals, it means that they would share the interface. All depends then how the behavior of a droid vs. a normal should be different:

  • The decorator proposed by Spotted is a nice alternative if your entities all share the same interface (e.g. walk()). However it would not suit your need if different entities would have different interfaces (e.g. talk() for human bark() for dogs)
  • The template based strategy pattern proposed by Jerry Coffin is a great alternative if the behavior of Dog/Cat/Human is based on some primitives defined by the template parameter. But you shall be aware that Dog<Droid> is not the same class than Dog<Normal>. It's just a more elegant way to generate the classes.
  • If keeping the same class is important for you, you could alternatively opt for a runtime Strategy pattern (i.e. instead of compile time template parameters, use a runtime parameter at instantiation). But be aware that in this case you'd loose compile-time checks and some compiler optimizations.

But as you're in game programming, it's worth to mention the component architecture exposed by Mike McShaffry in Game Coding Complete. He calls entities actors, and actors are flat.

His position is that the inheritance is there only to add features/behaviors to the actors. To keep flexibility to the maximum, he therefore sees actors as a simple collection of components which bear the features by composition (graphic representation components, behavioral components,etc.).

Actors are then created by a factory that assembles the components (e.g. for a droid human, add the usual human components, but add a synthetic voice filter instead of the smooth human voice reader, and add a power cell with energy capacity, and the droid fighting strategy).

This facilitates creation or combination of new species. But it also makes the structure more complex as it combines several design patterns at once.

  • 1
    Thank you for the clarification, I think I am going to use the strategy pattern. As you pointed out, with the decorator pattern I couldn't have humans talking and dogs barking. Besides, I'm very interested in the last design you mentioned. Having a factory assembling blocks to create classes at will, I like this idea very much. But I think that it's a bit overkill for just what I want. – qreon Aug 26 '16 at 11:29
  • Lastly, I'm ok with Dog<Normal> not being the same as Dog<Droid>, as long as they're both Dogs I can do what I want. And I think it won't be much of a hassle to have some pattern making it easy to select all Droids or all Normal. So I think I'll go with the strategy pattern. Thank you again! – qreon Aug 26 '16 at 11:30
1

Since you are using C++, you can even just use multiple inheritance to your advantage:

class Entity {...};

class Human : virtual public Entity {...};
class Dog : virtual public Entity {...};
class Cat : virtual public Entity {...};

class Normal : virtual public Entity {...};
class Droid : virtual public Entity {...};
class Void : virtual public Entity {...};

class DroidHuman : public Human, public Droid {...};

If most of the fully specified classes do not need any special code, you can even use a template at that point:

template<class Species, class Race>
class GameEntity : public Species, public Race {...};

With that, you can easily instantiate a void-dog where you need it:

GameEntity<Void, Dog> myPet;

But, of course, the decorator pattern is more flexible (see Spotted's answer).

  • Although it doesn't completely fit with OP's requirement of keeping only the original classes, I love this alternative that is the most flexible, since you could then have Droid specific functions (e.g. load_battery, search_energy, ...), and you could override a specific functions, if the general composition rule imposed by decorator or strategy wouldn't fit in certain cases. +1 – Christophe Aug 20 '16 at 18:58
0

If I understand the game right, a droid cat is not a special kind of cat. It is a state of a normal cat, like a zombi cat would be. Not a new instance but something your ordinary existing cat instance could transform into. And that is probably why inheritance and/or polimorphism does not feel right.

This does not in any way invalidate (more) inheritance or support the notion that "composition is better". More inheritance is just not a good fit for the problem you describe.

So, I think you would be better off implementing droid modes for your existing entities. The first thing could be a property IsDroid. I have no idea how droidness would impact behavior, I imagine a droid cat would use a different set of configuration parameters.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.