本文档将解释 Liskov 替换原则 (LSP),这是面向对象编程 SOLID 原则中的第三个原则,以及如何在 JavaScript 中正确应用它。该原则侧重于父类和子类之间的关系,并确保适当的可替换性。
有关其他 SOLID 原则的信息,请参阅概述页面 SOLID 原则、单一职责原则、开闭原则、接口隔离原则 或 依赖倒置原则。
Liskov 替换原则指出
如果 S 是 T 的子类型,那么类型 T 的对象可以被类型 S 的对象替换,而不会改变程序的任何可取属性。
简而言之,派生类必须可以替换其基类,而不会引起意外行为。
在 JavaScript 中,LSP 意味着当我们通过继承扩展类时,子类应该保持父类的所有行为,而不会改变其根本工作方式。使用父类的客户端应该能够使用其任何子类型而无需知道差异。
用于说明 LSP 违反的最常见示例是矩形-正方形问题。从数学上讲,正方形是矩形,因此似乎很自然地可以通过继承来建模。然而,这可能导致可替换性问题。
在此示例中,Square 类继承自 Rectangle,但修改了 setWidth 和 setHeight 的行为。当客户端期望矩形保持独立的宽度和高度值时,使用 Square 会产生意外的结果。
在存储库中显示的代码中,当 renderLargeRectangles 函数操作包含 Rectangle 和 Square 实例的列表时,存在明显的违反。
Square 类覆盖了 Rectangle 的方法。
setWidth 时,它还会更改高度。setHeight 时,它还会更改宽度。这意味着在 Square 上调用 rectangle.setWidth(4) 然后调用 rectangle.setHeight(5) 会导致两个维度都为 5,这违反了客户端的期望。
更好的方法是创建一个通用抽象(例如 Shape),并实现满足其合同的专门类,而不会违反该原则。
在此修正的设计中
Rectangle 和 Square 都继承自 Shape。getArea() 版本。Shape 抽象,而不依赖于矩形的特定行为。在 JavaScript 中使用继承时,请遵循以下准则来遵守 LSP:
确保行为一致性:子类不应更改父类继承方法的预期行为。
避免加强先决条件:子类方法不应比其父类方法要求更具体的输入。
避免减弱后置条件:子类方法应至少满足与其父类方法相同的保证。
在适当的时候优先使用组合而非继承,以避免 LSP 违反。
测试可替换性:验证所有子类是否可以在所有上下文中替换其父类。
Liskov 替换原则与其他 SOLID 原则协同工作。
| 原则 | 与 LSP 的关系 |
|---|---|
| 单一职责 | 具有单一职责的类更容易实现可替换性。 |
| 开闭 | LSP 通过允许通过可替换的子类型进行安全扩展,从而实现了开闭原则。 |
| 接口隔离 | 更小的接口更容易保持 LSP 合规性。 |
| 依赖倒置 | 依赖抽象而非具体实现,有利于可替换性。 |
尽管 JavaScript 是一种动态类型语言,没有正式的接口,但 LSP 仍然可以通过“鸭子类型”和对象之间的隐式合同来应用。JavaScript 开发人员应确保旨在互换使用的对象保持一致的行为模式。
在现代 JavaScript 应用程序中,当以下情况发生时,LSP 尤为重要:
Liskov 替换原则确保程序可以在预期父类对象的地方安全地使用子类对象。通过遵循 LSP,您可以创建更健壮、更易于维护的代码库,并具有可预测的继承层次结构。该原则鼓励您仔细考虑您的抽象以及类之间的合同,从而做出更好的设计决策。