LSP 和虚拟方法

问题描述 投票:0回答:1

我在尝试了解有关 LSP 的更多信息时遇到了这个示例,我是 OOP 世界的新手。你可以在例子后面找到我的问题。示例如下。

在数学中,正方形就是长方形。事实上,它是矩形的特化。 “is a”让你想用继承来建模。但是,如果在代码中您使 Square 从 Rectangle 派生,那么 Square 应该可以在您期望使用 Rectangle 的任何地方使用。这会导致一些奇怪的行为。

假设您的 Rectangle 基类上有 SetWidth 和 SetHeight 方法;这看起来完全符合逻辑。但是,如果您的 Rectangle 引用指向 Square,则 SetWidth 和 SetHeight 没有意义,因为设置其中一个会更改另一个以匹配它。在这种情况下,Square 未能通过 Rectangle 的里氏替换测试,并且让 Square 继承 Rectangle 的抽象是一个糟糕的抽象。

我的问题是:我们在这里谈论什么样的方法?我们谈论的是可以被重写的虚拟方法吗?如果不是,我看不到派生类可以提供它自己的实现的场景。在上面提到的示例中,如果方法 SetWidth 和 SetHeight 都是属于基类的非虚方法,并且分别设置宽度和高度,那么这些方法应该可以正常工作。那么,这是关于标准,而不是关于按预期工作的方法吗?按照标准,我的意思是,拥有可从方形对象访问的方法 SetWidth 和 SetHeight 是没有意义的,尽管它有效。

我没有尝试任何事情,我遇到了这种困惑,并认为我应该寻找一些答案。

function virtual-functions liskov-substitution-principle
1个回答
0
投票

这里的关键是LSP是关于合约以及这个合约保证什么的。 这与代码无关。

许多符合 LSP 的设计允许

Square
成为
Rectangle
的专业化。 以这个设计为例,其中形状是不可变的,并且转换仅返回一个新形状,而不对该形状可能是什么做出任何承诺:

UML class diagram with Square inheriting from Rectangle inheriting from abstract class Shape

在任何地方,只要使用

Shape
Rectangle
的实例,都可以使用
Square
的实例来代替。请记住,在这方面,构造函数被排除在 LSP 约束之外,根据该原则的起源 Liskov 和 Wing

对象通过创建者而存在并获得其初始值。与其他类型的方法不同,创建者不属于特定对象,而是独立的操作。

然而,对这种符合 LSP 的设计进行轻微改动就很容易破坏 LSP。 例如,添加操作

changeSize(int X, int Y)
将不再允许在使用矩形的任何地方使用正方形:每次 X 轴和 Y 轴上的大小发生不同变化时,正方形的不变量就会被破坏,因此需要进行操作对矩形有效,对正方形无效。

您的示例略有不同。 LSP 合规性将取决于您在

setWidth()
上设置的合同(以及
setHeight()
同样):如果后置条件是
width
具有新值并且
height
未更改,则存在侵权。 但这是你在合同中做出的承诺吗?如果后置条件仅约为
width
并且不能保证
height
保持不变,则设计仍然符合 LSP。

LSP实际上不是用方法来定义的,而是用可证明的谓词来定义的(基于合约)。 因此,无论我们讨论的是虚拟成员函数还是普通成员函数,都没有什么区别。 许多 OOP 语言甚至没有这种区别,并且所有函数都是可重写的。 然而,就 C++ 而言,如果您进行多态设计,即使用子类型而不是超类型,那么构建类层次结构并使方法虚拟化是一个很好的反射(例如,请参阅此C++ 核心指南) ).

© www.soinside.com 2019 - 2024. All rights reserved.