Избежание RTTI в дизайне OO

Недавно я рассмотрел вопрос дизайна OO на каком-то форуме и начал думать об использовании RTTI. Однако это должен быть плохой дизайн, но я не могу думать об альтернативе. Вот простой вопрос:

Создайте С++-программу для следующего сценария, используя концепции OO -

Моя собака, по имени Бадди, живет на заднем дворе. Он лает ночью, когда видит кошку или белку, которая пришла в гости. Если он видит лягушку, и он голоден, он ест ее. Если он видит лягушку, и он не голоден, он играет с ним. Если он уже съел 2 лягушек и все еще голоден, он отпустит его. Если он увидит койота, он плачет о помощи. Когда-то его друг Спот останавливается, и они преследуют друг друга. Если он видит любого другого животного, он просто наблюдает за ним. Я ожидал бы, что у вас будет класс животных, а также кошка, собака, белка, класс койот, который унаследует от класса животных.

Я начал думать о наличии метода see() в классе dog, который принимает аргумент Animal, а затем проверяет фактический тип объекта (frog, cat и т.д.) и принимает требуемые действия - играть, преследовать и т.д. в зависимости от фактический тип. Однако для этого потребуется RTTI, который должен быть плохим дизайном. Может ли кто-нибудь предложить лучший дизайн, который позволит избежать RTTI, а также указать на ошибку в моем мышлении?

Ответ 1

Существует невероятно большое количество способов решить эту проблему, используя "концепции OO", в зависимости от того, что вы хотите подчеркнуть.

Здесь самое простое решение, которое я могу придумать:

class Animal {
public:
    virtual void seenBy(Buddy&) = 0;
};

class Buddy {
public:
    void see(Cat&)      { /* ... */ }
    void see(Squirrel&) { /* ... */ }
    // ...
};

class Cat : public Animal {
public:
    virtual seenBy(Buddy& b) { b.see(*this); }
};

class Squirrel : public Animal {
public:
    virtual seenBy(Buddy& b) { b.see(*this); }
};

// classes for Frog, Coyote, Spot...

Если вам нужно несколько видов "воспринимающих" животных, просто сделать виртуальную оболочку для see (создавая форму двойная отправка):

// On a parent class
virtual void see(Animal&) = 0;

// On Buddy
virtual void see(Animal& a) { a.seenBy(*this); }

Вышеизложенное требует, чтобы класс Animal знал что-то о классе Buddy. Если вам не нравятся ваши методы, являющиеся пассивными глаголами, и вы хотите отделить Animal от Buddy, вы можете использовать шаблон посетителя:

class Animal {
public:
    virtual void visit(Visitor&) = 0;
};

class Cat : public Animal {
public:
    virtual void visit(Visitor& v) { v.visit(*this); }
};

class Squirrel : public Animal {
public:
    virtual void visit(Visitor& v) { v.visit(*this); }
};

// classes for Frog, Coyote, Spot...

class Visitor {
public:
    virtual void visit(Cat&) = 0;
    virtual void visit(Squirrel&) = 0;
    // ...
};

class BuddyVision : public Visitor {
public:
    virtual void visit(Cat&)      { /* ... */ }
    virtual void visit(Squirrel&) { /* ... */ }
    // ...
};

class Buddy {
public:
    void see(Animal& a) {
        BuddyVision visitor;
        a.visit(visitor);
    }
};

Второй механизм можно использовать для других целей, кроме Бадди, наблюдая за животным (возможно, для этого животного, наблюдающего Бадди). Это, однако, сложнее.


Обратите внимание, что OO определенно не единственный способ решить эту проблему. Существуют и другие решения, которые могут быть более практичными для этой проблемы, такие как хранение свойств различных животных, которые заставляют Бадди лаять, есть, играть и т.д. Это дополнительно отделяет класс Buddy от класса Animal (даже шаблон посетителя нуждается в исчерпывающем списке всего, что может видеть Бадди).

Ответ 2

Дизайн специально требует распознавания определенных объектов для выполнения определенных операций над ними. Поскольку нет рифмы или причины в отношении того, почему определенные операции идут с определенными объектами (т.е. Все это произвольно), то, что вы смотрите, это отправка на основе типа или отправка на основе свойств. Я бы пошел с последним.

Дайте каждой сущности некоторый набор свойств. Таким образом, собака реагирует на эти свойства. У Кошки и Белки было бы свойство: "Собака должна лаять на меня". Когда Собака встречает сущность с таким свойством, она выполнит соответствующее действие.

В этом случае объект представляет собой не что иное, как сумму его свойств, а также поведение, основанное на встрече с другими объектами с различными свойствами. У объекта также может быть определенное состояние, связанное с ним. Не было бы определенного класса собак или кошек. Там будет только объект с Cat-подобными свойствами и поведением, а также объект с собачьими свойствами и поведением.

Ответ 3

Подсказка: используйте виртуальные функции (на целевых животных) вместо RTTI.

Ответ 4

В большинстве случаев вы можете заменить RTTI на обмен сообщениями.

Сортировка

Id id = object->send(WHO_ARE_YOU); 
switch(id)
{
  case ID_FROG: ...; break;
  case ID_CAT: ...; break;
}

Сообщения более гибкие, чем RTTI в принципе:

other_object->send(IS_SCARRY_OF, this); 

поскольку он позволяет создавать отношения, которые на данный момент неизвестны. Скажем, завтра ваша собака увидит racoon, который определен в некоторых других DLL и все же в Pascal.