Принцип подстановки Лискова и множественные иерархии

Этот вопрос является продолжением этого. Я пытаюсь определить класс heirarchies с участием нескольких пар основанных на базе. В качестве наглядного примера предположим, что у меня есть класс Animal и класс Food, Animal имеет чисто виртуальную функцию, чтобы отмечать свои предпочтения в еде, принимая пищу в качестве параметра.

class Food
{
    public:
    virtual void printName() {
    //......
    }
};

class Animal
{
    public:
    Food *_preferredFood;
    virtual void setFoodPreference(Food *food)=0;

};

Мне нужно написать код, который работает только с этими базовыми классами и вызывает чисто виртуальную функцию. Например, у меня есть ZooManager класс, который устанавливает пищевые предпочтения для каждого животного.

class ZooManager
{
    vector<Aninal*> animals;
    public:
    void setAllPreferences(vector<Food *> foods) {
        assert(animals.size() == foods.size());
        for(int i =0;i<animals.size();i++) {
            animals[i]->setFoodPreference(foods[i]);
        }
    }
};

Все идет нормально. Теперь проблема в том, что есть много различных производных классов для Food а также Animal, Food вывел классы Fruit а также Meat, а также Animal вывел классы Carnivore а также Herbivore, Herbivore могу принять только Fruit в качестве пищевых предпочтений, и Carnivore могу принять только Meat,

class Fruit : public Food
{
};
class Meat : public Food
{
};
class Carnivore: public Animal
{
    public:
    void setFoodPreference(Food *food) {
    this->_preferredFood = dynamic_cast<Meat *>(food);
    }
};
class Herbivore: public Animal
{
    public:
    void setFoodPreference(Food *food) {
    this->_preferredFood = dynamic_cast<Fruit *>(food);
    }
};

Могу ли я создать классную иерархию для этого, не нарушая принцип подстановки Лискова? Хотя я использую C++ в этом вопросе, я бы также приветствовал ответы, специфичные для Java.

2 ответа

Решение

Во-первых, ваш setFoodPreference должен иметь возможность потерпеть неудачу. Это позволяет setFoodPreference взять Food* и имейте постусловие или установки предпочтения еды, или неудачи.

Динамическое приведение также может быть ошибкой LSP, но если вы сделаете инварианты типов достаточно расплывчатыми, это технически не будет ошибкой.

В общем-то, dynamic_cast означает, что тип передаваемого аргумента и его свойства недостаточны, чтобы определить, имеет ли аргумент определенные свойства.

В принципе, setFoodPreference(Food*) должно быть указано с точки зрения того, что Food* свойства, которые должны быть переданы в аргументе, чтобы настройка была успешной; динамический тип Food* это не Food* имущество.

Итак: LSP утверждает, что любой подкласс Food должен подчиняться всем Food инварианты. Аналогично для Animal, Вы можете избежать нарушения LSP, сделав инварианты расплывчатыми, а поведение методов непредсказуемым; в основном, говоря "это может потерпеть неудачу по неуказанным причинам". Это... не очень приятно.

Теперь вы можете сделать шаг назад и решить, что динамический тип вашего Food* является частью Food*интерфейс пользователя; это делает интерфейс смехотворно широким и насмешливым над LSP.

Смысл LSP в том, что вы можете рассуждать о Food* без необходимости думать о его типах подкласса; они "как это работает как FoodMsgstr "Ваш код тесно связан с типами подкласса и, таким образом, обходит точку LSP.

Есть способы обойти это. Если Food было перечисление, указывающее, что это была за еда, и вы никогда не Meat а скорее спросил Food если это было мясо, вы избегаете его. Теперь вы можете указать setFoodPreferenceповедение с точки зрения FoodИнтерфейс.

Ваш подход к проектированию иерархий неверен. ОО-классы представляют группы тесно связанных правил, где правило является функцией, а тесно связанные - общими данными. ОО классы НЕ представляют объекты реального мира. Люди ошибаются, когда говорят это.

Если вы зависите от конкретного типа Пищи, вы нарушаете принцип замещения Лискова. Период.

Чтобы правильно спроектировать свои классы, чтобы вас не заставляли нарушать LSP, как вы здесь, вам нужно поместить правила в класс Food, которые могут использоваться классом Animal для выполнения своих собственных правил. Или решить, должна ли Еда быть даже классом. То, что показывает ваш пример, по сути, очень плохое сравнение строк.

Другие вопросы по тегам