此问题是 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
和草食动物
。 草食动物
只能接受水果
作为食物偏好,而食肉动物
只能接受肉类
。
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);
}
};
我可以在不违反里氏替换原则的情况下为此创建一个类层次结构吗?虽然我在这个问题中使用了 C++,但我也欢迎特定于 Java 的答案。
最佳答案
首先,您的 setFoodPreference
必须具有失败的选项。这让 setFoodPreference
接受 Food*
并具有设置食物偏好或失败的后置条件。
动态转换也可能是 LSP 的失败,但如果您将类型不变量安排得足够模糊,那么从技术上讲,这并不是失败。
通常,dynamic_cast
意味着传递的参数类型及其属性不足以判断该参数是否具有某些属性。
原则上,setFoodPreference(Food*)
应根据传入参数必须具有的 Food*
属性来指定,以便设置成功; Food*
的动态类型不是 Food*
属性。
所以:LSP 规定 Food
的任何子类都必须遵守所有 Food
不变量。对于动物
也是如此。您可以通过使不变量变得模糊并且方法的行为变得不可预测来避免 LSP 违规;基本上是说“它可能会因未指定的原因而失败”。这……不太令人满意。
现在,您可以退后一步,确定 Food*
的动态类型是 Food*
的一部分界面;这使得接口(interface)变得异常宽泛,并且 mock 了 LSP。
LSP 的要点是您可以推理 Food*
而无需考虑其子类类型;它们是“它作为食物
的工作原理”。您的代码与子类类型紧密绑定(bind),从而绕过了 LSP 点。
有一些方法可以解决这个问题。如果 Food
有一个枚举来说明它是什么类型的食物,并且您永远不会动态地向下转换为 Meat
而是询问 Food
是否是肉,你就避开它。现在,您可以根据 Food
的界面指定 setFoodPreference
的行为。
关于java - 里氏替换原则和多重层次结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36315970/