菜单

里氏替换原则

相关源文件

目的与范围

本文档将解释 Liskov 替换原则 (LSP),这是面向对象编程 SOLID 原则中的第三个原则,以及如何在 JavaScript 中正确应用它。该原则侧重于父类和子类之间的关系,并确保适当的可替换性。

有关其他 SOLID 原则的信息,请参阅概述页面 SOLID 原则单一职责原则开闭原则接口隔离原则依赖倒置原则

定义

Liskov 替换原则指出

如果 S 是 T 的子类型,那么类型 T 的对象可以被类型 S 的对象替换,而不会改变程序的任何可取属性。

简而言之,派生类必须可以替换其基类,而不会引起意外行为。

来源: README.md1532-1539

理解可替换性

在 JavaScript 中,LSP 意味着当我们通过继承扩展类时,子类应该保持父类的所有行为,而不会改变其根本工作方式。使用父类的客户端应该能够使用其任何子类型而无需知道差异。

图示:Liskov 替换原则概念

来源: README.md1540-1545

经典的矩形-正方形问题

用于说明 LSP 违反的最常见示例是矩形-正方形问题。从数学上讲,正方形是矩形,因此似乎很自然地可以通过继承来建模。然而,这可能导致可替换性问题。

图示:矩形-正方形违反

在此示例中,Square 类继承自 Rectangle,但修改了 setWidthsetHeight 的行为。当客户端期望矩形保持独立的宽度和高度值时,使用 Square 会产生意外的结果。

来源: README.md1543-1600

代码示例:LSP 违反

在存储库中显示的代码中,当 renderLargeRectangles 函数操作包含 RectangleSquare 实例的列表时,存在明显的违反。

Square 类覆盖了 Rectangle 的方法。

  • 调用 setWidth 时,它还会更改高度。
  • 调用 setHeight 时,它还会更改宽度。

这意味着在 Square 上调用 rectangle.setWidth(4) 然后调用 rectangle.setHeight(5) 会导致两个维度都为 5,这违反了客户端的期望。

来源: README.md1549-1600

符合 LSP 的解决方案

更好的方法是创建一个通用抽象(例如 Shape),并实现满足其合同的专门类,而不会违反该原则。

图示:符合 LSP 的设计

在此修正的设计中

  1. RectangleSquare 都继承自 Shape
  2. 每个类都实现其适合其几何形状的 getArea() 版本。
  3. 客户端使用 Shape 抽象,而不依赖于矩形的特定行为。

来源: README.md1602-1647

保持 LSP 的最佳实践

在 JavaScript 中使用继承时,请遵循以下准则来遵守 LSP:

  1. 确保行为一致性:子类不应更改父类继承方法的预期行为。

  2. 避免加强先决条件:子类方法不应比其父类方法要求更具体的输入。

  3. 避免减弱后置条件:子类方法应至少满足与其父类方法相同的保证。

  4. 在适当的时候优先使用组合而非继承,以避免 LSP 违反。

  5. 测试可替换性:验证所有子类是否可以在所有上下文中替换其父类。

与其他 SOLID 原则的集成

Liskov 替换原则与其他 SOLID 原则协同工作。

原则与 LSP 的关系
单一职责具有单一职责的类更容易实现可替换性。
开闭LSP 通过允许通过可替换的子类型进行安全扩展,从而实现了开闭原则。
接口隔离更小的接口更容易保持 LSP 合规性。
依赖倒置依赖抽象而非具体实现,有利于可替换性。

JavaScript 中的实际应用

尽管 JavaScript 是一种动态类型语言,没有正式的接口,但 LSP 仍然可以通过“鸭子类型”和对象之间的隐式合同来应用。JavaScript 开发人员应确保旨在互换使用的对象保持一致的行为模式。

在现代 JavaScript 应用程序中,当以下情况发生时,LSP 尤为重要:

  1. 在 React 等框架中创建组件层次结构。
  2. 开发插件系统,其中不同的实现必须满足相同的合同。
  3. 构建 API 客户端,其中不同的提供者必须遵循一致的模式。
  4. 处理事件处理程序和回调函数。

总结

Liskov 替换原则确保程序可以在预期父类对象的地方安全地使用子类对象。通过遵循 LSP,您可以创建更健壮、更易于维护的代码库,并具有可预测的继承层次结构。该原则鼓励您仔细考虑您的抽象以及类之间的合同,从而做出更好的设计决策。

来源: README.md1532-1647