在子类型中使用额外的构造函数参数违反了 LSP 原则

Violation of the LSP-principle using extra constructor parameters in subtypes

当我注意到 this answer 时,我一直在阅读里氏替换原则。它有一个 Circle 和一个 ColoredCircle 类型,其中 ColoredCircle 的构造函数需要一个额外的参数; color.

class Circle:
    radius: int

    def __init__(self, radius: int) -> None:
        self.radius = radius

class ColoredCircle(Circle):
    radius: int
    color: str

    def __init__(self, radius: int, color: str) -> None:
        super().__init__(radius)
        self.color = color

这是否违反了以下要求之一? (taken from this answer)。在 ColoredCircle 的情况下,唯一的其他选项是 public 变量或 set_color 方法。

Pre-conditions cannot be strengthened: Assume your base class works with a member int. Now your sub-type requires that int to be positive. This is strengthened pre-conditions, and now any code that worked perfectly fine before with negative ints is broken.

如果我在这里搜索的方向有误,请告诉我。另外,如果一个子类型有更多的参数要处理,通常如何管理这些参数,新的抽象总是 nessacery 吗?

Doesn't this violate one of the requirements below?

这取决于语言;但至少在 Java 中,构造函数不是继承的,因此它们的签名不受管理继承的 LSP 的约束。

子类型最适合修改(超类型的)行为。这称为多态性,子类型做得很好(当遵循 LSP 时)。子类型在代码重用方面表现不佳,例如共享变量。这就是著名原则 prefer composition over inheritance.

背后的思想

当 class X 具有构造函数时,构造函数不是 X 类型对象的方法。由于它不是 X 类型对象的方法,因此它也不必作为派生类型对象的方法存在——它与 LSP 无关。

Liskov 替换原则的目的是类型及其子类型应该是可替换的,这反过来又允许解耦。消费者不必知道对象的实现,只需要知道其声明的类型。如果 class 通过调用其构造函数创建自己的依赖项,则它会耦合到该特定类型。在这种情况下,LSP 变得无关紧要。没有办法替换另一种类型,所以类型是否可替换并不重要。

换句话说 - 创建另一个 class 实例的 class 通常无法从 LSP 中受益,因为它主要排除了用一种类型替换另一种类型的可能性。如果它将该对象传递给其他以创建它以外的方式与其交互的方法,那就是我们从 LSP 中受益的地方。

基于这个推理,我会说不同的构造函数不会违反 Liskov 替换原则的意图。

在许多语言中,我们使用依赖注入来将 classes 从依赖结构中分离出来。这意味着消费者处理类型 除了 的构造函数的每个方面。构造函数不在等式中,子类型可以(或应该)替代它们继承的类型。