利斯科夫代换原理(简化)

2020-05-20 22:15:46

1987年,在发表关于数据抽象和层次结构的主旨演讲时,Barbara Liskov介绍了最终成为Liskov替换原则的想法。以下是对该原则的现代(且非常正式)描述:

设Φ(X)是关于类型T的对象x可证明的性质,则Φ(Y)对于类型S的对象y应该为真,其中S是T的子类型。

当我第一次读到定义时,我所理解的就是它在某种程度上与继承有关……。也许吧?。我想出了一个更简单(虽然不太完整)的短语,可能更容易理解:

依赖于类型P的对象的一段代码应该能够正确操作类型C的对象,其中C是P的子类型。

这意味着我们应该以促进整个层次结构之间的互操作性的方式设计我们的抽象和类。如果一个子类以意想不到的方式覆盖了行为,破坏了与其余代码的兼容性,我们就违反了这一原则。

通过示例理解LSP要容易得多。让我们来看看罗伯特·C·马丁提出的正方形问题。

我们都同意正方形就是长方形的观点。从定义上讲,这句话是正确的,但像许多其他事情一样,它在编程领域可能不会很好地工作。

假设我们有以下代码,其中我们断言Rectangle的get_area函数运行正常:

矩形=#.。从某个地方获取Rectangle对象,可能factoryrectangle.set_height(5)rectangle.set_width(4)raise";面积计算已关闭";除非矩形.get_Area==20#.。更多代码#在另一个文件中,您可以找到矩形类Rectangle def initialize()end def set_width(Width)@width=width end def set_Height(Height)@Height=Height end def get_area return@width*@Height endend的假设实现。

现在,假设我们实现了一个Square类,根据定义它也是一个矩形。您不能单独设置正方形的宽度和高度,这两个值对于正方形来说总是相同的。

矩形=#.。此行现在给出了一个Square对象#其余代码保持不变#在另一个文件中,您可以找到Square类的假设实现Square<;Rectangle def set_width(Width)set_side(Width)end def set_Height(Height)set_side(Height)end def set_side(Side_Length)@Height=side_length@width=side_length endend。

如果我们运行此代码,断言将失败,因为面积现在为16。使用类型为Rectangle(及其子类型)的对象的代码与Square中断,即使它实现与Square相同的公共接口和行为也是如此。

LSP与类型层次结构中的语义一致性有关。仅在上述层次结构中实现相同的公共接口是不够的,行为也应该是一致的。

我们的目标是互操作性,以及在不需要特殊处理异常值的情况下使用子类型的能力。

在我们的示例中,我们可能会尝试使用基于类型的if语句来解决问题:如果我们发现一个类型为Square的对象,请执行X个不同的操作。这是一个很容易失控的问题,会随着代码的增长污染依赖于矩形的代码段。

我们需要的是一种有纪律的方法:只要我们使用相同的公共接口,实现Rectangle公共接口的每个类型都应该像矩形一样工作。

与静态类型语言中的传统类层次结构相比,该原则有更广泛的应用。它也用于与鸭子类型共享公共接口,或设计具有共享REST接口的微服务。

凉爽的!。现在你们也知道了S.O.L.I.D原则中的L。了解此主题对于做出正确的架构选择非常重要。我希望你在将来的项目中能用到它。

与朋友和同事分享这篇文章。感谢您帮助我联系到可能会觉得此信息有用的人。

在“清洁架构”的第9章中有更多关于OCP的信息。这本书和其他非常有帮助的书都在推荐书单上。

给我发一封有问题、评论或建议的电子邮件(它在关于我的页面上)。来吧,别害羞!