Liskov替换原则和多层次结构

时间:2016-03-30 17:42:23

标签: java c++ oop inheritance liskov-substitution-principle

这个问题是this的后续问题。我试图定义涉及多个基础派生对的类层次结构。作为一个说明性示例,假设我有一个类Animal和一个类FoodAnimal有一个纯虚函数来标记其食物偏好,以食物为参数。

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

到目前为止一切顺利。现在的问题是,FoodAnimal有许多不同的派生类。 Food派生了类FruitMeatAnimal派生了类CarnivoreHerbivoreHerbivore只能接受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);
    }
};

我是否可以在不违反Liskov替换原则的情况下为此创建一个类heirarchy?虽然我在这个问题中使用C ++,但我也欢迎特定于Java的答案。

2 个答案:

答案 0 :(得分:2)

首先,您的setFoodPreference必须选择失败。这样,setFoodPreference可以获得Food*并具有设置食物偏好或失败的后置条件。

动态强制转换也可能是LSP的失败,但是如果你将类型不变量排列得足够模糊,那么从技术上讲它就不是失败。

通常,dynamic_cast表示传递的参数类型及其属性不足以判断参数是否具有某些属性。

原则上,setFoodPreference(Food*)应该根据传递参数必须具有的Food*属性来指定,以便设置成功; Food*的动态类型不是Food*属性。

所以:LSP声明Food的任何子类都必须服从所有Food不变量。同样适用于Animal。您可以通过使不变量模糊且方法行为不可预测来避免LSP违规;基本上是说“它可能由于未指明的原因而失败”。这......不太令人满意。

现在,您可以退后一步,确定Food*的动态类型是<{1}}界面的部分;这使界面变得非常宽泛,并且嘲弄了LSP。

LSP的要点是你可以推理Food*,而不必考虑它的子类类型;它们是“它如何作为Food*”工作。您的代码与子类类型紧密绑定,从而绕过LSP的点。

有很多方法可以解决这个问题。如果Food有一个枚举说明它是什么类型的食物,你永远不会动态地归结为Food而是问Meat它是否是肉,你就避免它。现在,您可以根据Food的界面指定setFoodPreference的行为。

答案 1 :(得分:1)

您设计层次结构的方法是错误的。 OO类表示紧密耦合规则的组,其中规则是函数并且紧密耦合意味着共享数据。 OO类不代表现实世界的对象。当他们这么说时,人们就错了。

如果您依赖于食物的具体类型,则违反了Liskov替代原则。周期。

要正确设计类以便不像在此处那样强行违反LSP,您需要在Food类中放置规则,Animal类可以使用这些规则来完成自己的规则。或者决定食物是否应该是一个班级。您的示例显示的基本上是非常糟糕的字符串比较。