Liskov Substitution Principle with multiple inheri

2019-09-05 15:49发布

问题:

I am trying to come up with an object oriented design, and having difficulty satisfying the Liskov Substitution Principle. Here is an illustrative example:

class Food
{
    public:
    virtual void printName() {
    //......
    }
};
class Fruit : public Food
{
};
class Meat : public Food
{
};
class Animal
{
    public:
    Food *_preferredFood;
    virtual void setFoodPreference(Food *food)=0;

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

How can I enforce the following:

  1. Each subclass of Animal should allow setting a food preference, without breaking LSP
  2. The food preference for each derived class of animal is a subclass of Food

Fore example, if somebody extends Animal to create MarineMammal, the food preference might be Fish (which they will create by extending Food).

回答1:

When Carnivore::setFoodPreference only accepts Meat and Herbivore::setFoodPreference only accepts Fruit, then they do not comply to the same contract. That means they are not actually the same method. When you call this method, you have to know if you are dealing with a carnivore or a herbivore to avoid passing the wrong type. When you forget to check this, you risk creating a bug which manifests in form of a casting error at runtime.

The solution is to separate these two methods.

I would recommend you to remove setFoodPreference from the public interface and instead add methods Carnivore::setMeatPreference(Meat *meat) and Herbivore::setFruitPreference(Fruit *fruit) directly to the subclasses. That way any code which sets the food preference must know what kind of Animal and what kind of Food it is dealing with, so you can no longer write code which tries to set an incompatible food type.

Internally, both methods may set a protected Food *_preferredFood from the common base class. Or even better, call a protected void setPreferredFood(Food *food) which is a setter for a private Food* _preferredFood. That variable should definitely not be public to ensure proper encapsulation.