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

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

当我注意到这个答案时,我一直在阅读里氏替换原理。它有一个

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

这是否违反了以下要求之一? (摘自这个答案)。在

ColoredCircle
的情况下,唯一的其他选项是公共变量或
set_color
方法。

无法强化先决条件:假设你的基类有效 与成员 int。现在你的子类型要求 int 为正数。 这是强化的先决条件,现在任何有效的代码 在负整数被破坏之前完全没问题。

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

python oop design-patterns solid-principles liskov-substitution-principle
4个回答
2
投票

当类

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


2
投票

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

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

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

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


1
投票
这是否违反了以下要求之一?

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

子类型最适合修改(超类型的)

行为

。这被称为多态性,并且子类型做得很好(当遵循 LSP 时)。子类型对于代码重用(例如共享变量)效果不佳。这就是著名原则背后的思想,优先选择组合而不是继承


0
投票

LSP 谈论并专注于替换

实例,而不是“类”本身!

有代码:

class Circle: def __init__(self, radius: int) -> None: self.radius = radius class ColoredCircle(Circle): def __init__(self, radius: int, color: str) -> None: super().__init__(radius) self.color = color circle_instance = Circle(10) colored_circle_instance = ColoredCircle(20, "blue") def print_radius(obj: Circle) -> None: print(f"The radius is: {obj.radius}")

无论您需要
Circle

类型的实例(即circle_instance

),都可以传递
ColoredCircle
类型的
实例(即colored_circle_instance
)。 LSP 在这里不会受到侵犯。您可以在 
circle_instance
 上、在 
colored_circle_instance
 上做任何可能的事情。如果您有实例,则意味着初始化程序之前必须已调用过。
什么时候会出问题?如果您有不兼容的方法,可以在实例上调用

class Circle: def __init__(self, radius: int) -> None: self.radius = radius def method(self, arg) -> None: print(f"calling method with arg: {arg}") class ColoredCircle(Circle): def __init__(self, radius: int, color: str) -> None: super().__init__(radius) self.color = color def method(self, arg, arg2) -> None: print(f"calling method with arg: {arg, arg2}") 甚至 Pylance 也会告诉你:

Method "method" overrides class "Circle" in an incompatible manner
  Positional parameter count mismatch; base method has 2, but override has 3PylancereportIncompatibleMethodOverride

有人可能会说类本身就是实例。是的,但那是另一个故事了。如果我们想讨论在传递实际“类”(
Circle

ColoredCircle

)时是否违反 LSP(例如用于实例化),则必须存在元类的层次结构。

    

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