Liskov 替换原则和多重层次结构

Liskov substitution principle and multiple hierarchies

此问题是 的后续问题。我正在尝试定义涉及多个碱基派生对的 class 层次结构。作为说明性示例,假设我有一个 class Animal 和一个 class FoodAnimal有一个纯虚函数来标记它的食物偏好,以食物为参数。

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

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

};

我需要编写仅处理这些基 class 的代码,并调用纯虚函数。例如,我有一个 ZooManager class,它设置了每个动物的食物偏好。

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 有许多不同的派生 classes。 Food 导出了 classes FruitMeat,并且 Animal 导出了 classes CarnivoreHerbivore . 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);
    }
};

我可以为此创建一个 class 层次结构而不违反 Liskov 替换原则吗?尽管我在这个问题中使用了 C++,但我也欢迎 Java 特定的答案。

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

动态转换也可能是 LSP 的失败,但如果您将类型不变量设置得足够模糊,从技术上讲,这并不是失败。

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

原则上,setFoodPreference(Food*)应该指定传入的参数必须具有哪些Food*属性才能设置成功; Food* 的动态类型不是 Food* 属性.

所以:LSP 声明 Food 的任何子类都必须遵守所有 Food 不变量。 Animal 同样如此。您可以通过使不变量模糊和方法的行为不可预测来避免违反 LSP;基本上是说 "it can fail for unspecified reasons"。这个……不是很满意

现在,您可以退后一步,确定您的 Food* 的动态类型是 Food* 接口的 部分 ;这使得界面宽得离谱,并嘲笑了 LSP。

LSP 的要点是您可以推理 Food* 而无需考虑其子类类型;他们是 "how it works as a Food"。您的代码与子类类型紧密绑定,从而绕过了 LSP 点。

有很多方法可以解决这个问题。如果 Food 有一个枚举说明它是什么食物,而你从不动态转换为 Meat 而是询问 Food 它是不是肉,你就避免了它。现在您可以根据 Food 的界面指定 setFoodPreference 的行为。

您设计层次结构的方法是错误的。 OO classes 表示一组紧耦合规则,其中规则是一个函数,紧耦合意味着共享数据。 OO classes 不代表真实世界的对象。人们这么说是错误的。

如果你依赖于食物的具体类型,你就违反了里氏替换原则。期间.

要正确设计您的 classes 以便您不会像在这里那样被迫违反 LSP,您需要在动物可以使用的食物 class 中放置规则class 完成自己的规则。或者决定 Food 是否应该是 class。你的例子显示的基本上是非常糟糕的字符串比较。