假设我们想模拟不同种类的食物:披萨和馅饼。披萨和馅饼都有一种配料,但它们的配料不同。
我们实现一个抽象类Baked
,以及两个具体类Pizza
和Pie
。我们还实现一个抽象类Topping
,以及两个具体类PizzaTopping
和PieTopping
。
根据定义, 的任何具体实现都Baked
必须具有Topping
,因此Baked
应具有 类型的属性Topping
。此外,此特定的 topping 属性应为PizzaTopping
中的类型Pizza
,并且PieTopping
中的类型Pie
。
如果我正确理解了前面的答案,那么在这种情况下 LSP 就会被违反,因为如果我们抛开抽象的事实Baked
,就有可能实例化Baked
持有 的 a ,但如果我们用PieTopping
替换,这是不可能的。Baked
Pizza
我的问题是:有没有办法在不违反 LSP 的情况下实现这个模型?
我将首先解决当前的问题,然后我将谈谈该问题所隐含的总体假设。
使用类似 C# 的伪代码,您可以
Pizza
像这样定义:然后对整个班级做同样的事情
Pie
。这可能会引发一个自然的问题:为什么还要有一个抽象
Topping
类?确实,根据 OP 中的信息,我们没有足够的信息来回答这样的问题。是否
Topping
定义了派生类可以实现的一些多态行为?如果是,那是什么?这些是否与里氏替换原则 (LSP) 有关?不幸的是,大学、训练营和在线课程都坚持教授面向对象设计,认为它就是在现实世界中建模具体对象。这可能是Simula 的初衷,但很少被证明是 Java 或 C# 等语言中构建代码的最佳方式。
LSP 不是关于结构,而是关于行为。维基百科条目实际上很好地概括了对 LSP 的现代理解,其中讨论了
所有这些(先决条件、后置条件和不变量)在Bertrand Meyer 的 OOD 观点中都是类型契约的一部分。只有类型的设计者才能说出契约是什么,而且由于 OP 没有说明这一点,我们无法真正回答给定的设计是否违反了 LSP。
让我们尝试仔细分析一下您的问题。
里氏替换原则(LSP):
在您的情况下,这意味着以下内容:如果您有一些代码(例如,方法
f
)作为Baked
参数,那么如果您将实例Pizza
或Pie
作为参数传递给它,它应该可以正常工作。从这句话可以看出,如果不理解方法,以及类和中方法的实现,我们就无法谈论是否符合 LSP 。因为
f
Baked
Pizza
Pie
这些信息直接影响对“程序正确性”的理解。