这个问题是this的后续问题。我试图定义涉及多个基础派生对的类层次结构。作为一个说明性示例,假设我有一个类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);
}
};
我是否可以在不违反Liskov替换原则的情况下为此创建一个类heirarchy?虽然我在这个问题中使用C ++,但我也欢迎特定于Java的答案。
答案 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类可以使用这些规则来完成自己的规则。或者决定食物是否应该是一个班级。您的示例显示的基本上是非常糟糕的字符串比较。