简体   繁体   中英

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.

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM