Phil Kang
Phil Kang

Reputation: 1018

Is it fine to violate Composition Over Inheritance when necessary?

I have a set of classes:


// This is #included from another header file
// I don't want to inherit this, because it ain't my code
class DrawableObject;

class Animal {
    DrawableObject obj;
    // Does not define run() or swim()
};

class Cat : public Animal {
    void run() { /* Calls obj.setPosition() and other stuff */ }
};

class Dog : public Animal {
    void run() { /* Calls obj.setPosition() and other stuff */ }
    void swim() { /* Calls obj.setPosition() and other stuff */ }
};

class Dolphin : public Animal {
    void swim() { /* Calls obj.setPosition() and other stuff */ }
};

Here, Dog::run() and Cat::run() happen to use the exact same code, and Dog::swim() and Dolphin::swim() also use the same code. Instead of copy-pasting code all over the place, I would like to reuse it. The sensible solution seems to be adding intermediate subclasses between the base class (Animal) and the concrete classes (Cat/Dog/Dolphin):

       /-> RunnableAnimal --> Cat
       |                  \
Animal-|                  |-> Dog
       |                  /
       \-> SwimmableAnimal -> Dolphin

The question being: Am I going against the "Composition over Inheritance" rule? If so, is this perfectly fine, or is there a way to adhere to CoI while achieving code reuse?

Note: I don't need or want polymorphism--when I use run(), I'm always calling it using the concrete (Cat/Dog/Sloth) classes, instead of the base Animal class.

Upvotes: 1

Views: 110

Answers (1)

Aconcagua
Aconcagua

Reputation: 25526

Better inheritance pattern:

          /–––––––––– Cat
         /          /
        /    Runner
       /            \
Animal –––––––––––––– Dog
       \            /
        \    Swimmer
         \          \
          \–––––––––– Dolphin

You avoid the diamond pattern you introduced with your approach.

Instead of inheriting, you might instead aggregate a Runner/Swimmer instance within the animals where needed and let the animals' functions just delegate to the members.

Just a minor issue regarding your model: It doesn't really reflect reality, actually, cats, although disliking water, are quite good swimmers as well...

Edit: As Runner and Swimmer need access to Animal's members: You can provide this via curiously recurring template pattern; added a demonstration below:

class Animal
{
protected:
    int n = 7;
};

template <typename T>
class Swimmer
{
public:
    void swim()
    {
        std::cout << static_cast<T*>(this)->n << std::endl;
    }
};

class Dolphin : public Animal, public Swimmer<Dolphin>
{
    friend class Swimmer; // n is protected!
    // (alternatively, Swimmer might already be a friend of Animal)
};


int main(int argc, char* argv[])
{
    Dolphin d;
    d.swim();

    return 0;
}

Upvotes: 4

Related Questions