This question is a follow up to this. I am trying to define class heirarchies involving multiple base-derived pairs. As an illustrative example, suppose that I have a class Animal
and a class Food
. Animal
has a pure virtual function to mark its food preference, taking a food as parameter.
class Food
{
public:
virtual void printName() {
//......
}
};
class Animal
{
public:
Food *_preferredFood;
virtual void setFoodPreference(Food *food)=0;
};
I need to write code that deals only with these base classes, and calls the pure virtual function. For example, I have a ZooManager
class, which sets food preferences for each animal.
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]);
}
}
};
So far so good. Now the problem is, that there are many different derived classes for Food
and Animal
. Food
has derived classes Fruit
and Meat
, and Animal
has derived classes Carnivore
and Herbivore
. Herbivore
can accept only Fruit
as food preference, and Carnivore
can accept only 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);
}
};
Can I create a class heirarchy for this without violating Liskov Substitution Principle? Although I use C++ in this question, I'd welcome Java-specific answers too.
First, your setFoodPreference
has to have the option to fail. That lets setFoodPreference
take a Food*
and have the postcondition of either setting the food preference, or failing.
The dynamic cast can also be a failure of LSP, but if you arrange your type invariants to be vague enough it isn't technically a failure.
Generally, dynamic_cast
means that the type of the argument passed and its properties isn't sufficient to tell if the argument has certain properties.
In principle, setFoodPreference(Food*)
should be specified in terms of what Food*
properties the passed in argument has to have in order for the setting to be successful; the dynamic type of the Food*
is not a Food*
property.
So: LSP states that any subclass of Food
has to obey all Food
invariants. Similarly for Animal
. You can avoid a LSP violation by making the invariants vague and the behavior of methods unpredictable; basically by saying "it can fail for unspecified reasons". This is ... not very satisfying.
Now, you can take a step back and decide that the dynamic type of your Food*
is part of the Food*
's interface; that makes the interface ridiculously broad, and makes a mockery of LSP.
The point of LSP is that you can reason about a Food*
without having to think about its subclass types; they are "how it works as a Food
". Your code binds tightly to the subclass types, and thus bypasses the point of LSP.
There are ways around this. If Food
had an enum stating what kind of food it was, and you never dynamically cast down to Meat
but rather asked the Food
if it was meat, you avoid it. Now you can specify setFoodPreference
's behavior in terms of Food
's interface.