Можно ли нарушать Композицию через Наследование при необходимости? - PullRequest
1 голос
/ 14 апреля 2019

У меня есть набор классов:


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

Здесь Dog::run() и Cat::run() случайно используют один и тот же код, а Dog::swim() и Dolphin::swim() также используют тот же код.Вместо того, чтобы копировать код повсюду, я хотел бы использовать его повторно.Кажется, что разумным решением является добавление промежуточных подклассов между базовым классом (Animal) и конкретными классами (Cat/Dog/Dolphin):

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

Вопрос заключается в следующем: иду ли я против "композиции над наследованием""правило?Если да, то это прекрасно, или есть способ придерживаться CoI при достижении повторного использования кода?

Примечание: я не не нуждаюсь или не хочу полиморфизма - когда я использую run(), я всегда называю это, используя конкретные (Cat/Dog/Sloth) классы вместо базового Animal класса.

1 Ответ

4 голосов
/ 14 апреля 2019

Лучшая модель наследования:

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

Вы избегаете ромбов, которые вы использовали в своем подходе.

Вместо наследования вы можете вместо этого агрегировать экземпляр Runner / Swimmer внутри животных, где это необходимо, и позволить функциям животных просто делегировать членам.

Просто небольшая проблема, касающаяся вашей модели: на самом деле это не отражает реальность, на самом деле, кошки, хотя и не любят воду, тоже неплохие пловцы ...

Редактировать: Поскольку Runner и Swimmer требуется доступ к Animal участникам: вы можете предоставить это через любопытно повторяющийся шаблон ; добавил демонстрацию ниже:

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