Принцип подстановки Лискова и множественные иерархии
Этот вопрос является продолжением этого. Я пытаюсь определить класс 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*
без необходимости думать о его типах подкласса; они "как это работает как Food
Msgstr "Ваш код тесно связан с типами подкласса и, таким образом, обходит точку LSP.
Есть способы обойти это. Если Food
было перечисление, указывающее, что это была за еда, и вы никогда не Meat
а скорее спросил Food
если это было мясо, вы избегаете его. Теперь вы можете указать setFoodPreference
поведение с точки зрения Food
Интерфейс.
Ваш подход к проектированию иерархий неверен. ОО-классы представляют группы тесно связанных правил, где правило является функцией, а тесно связанные - общими данными. ОО классы НЕ представляют объекты реального мира. Люди ошибаются, когда говорят это.
Если вы зависите от конкретного типа Пищи, вы нарушаете принцип замещения Лискова. Период.
Чтобы правильно спроектировать свои классы, чтобы вас не заставляли нарушать LSP, как вы здесь, вам нужно поместить правила в класс Food, которые могут использоваться классом Animal для выполнения своих собственных правил. Или решить, должна ли Еда быть даже классом. То, что показывает ваш пример, по сути, очень плохое сравнение строк.