Reputation: 1018
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
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