繁体   English   中英

重载(而不是压倒一切)是否违反了 Liskov 替换原则?

[英]Does overloading (not overriding) break the Liskov subsitution principle?

有大量关于 LSP 的讨论,但所有讨论似乎都过于模糊。
AFAIK,LSP 指出要正确覆盖(而不是重载)子类中的超类方法,应确保子方法:

  1. 不会产生父方法在任何情况下都不会引发的新异常类型
  2. 与父方法具有相同的签名(在强类型语言的情况下)
  3. 与签名具有相同的语义含义
  4. 返回相同类型的值
  5. 返回具有相同语义的值

通过semantic meaning ,我的意思是,如果基本 class 方法暗示它返回int并且这个int意味着,比如说,美元或欧元,那么重载的方法还应该暗示返回值是美元或欧元,并且即使在一种情况会违反 LSP。

但是如果我的基础 class 看起来像这样(示例在 Python 中):

class A:

    func(x: int) -> int
        return x*2

class B(A):

    func(x: int, y: string) -> int
        return x*y

所以我的问题中有两个子问题:

  1. contract这个术语在更实际的意义上是什么意思? interface的同义词吗?
  2. 使用基础 class 中不存在的签名(作为参数列表的一部分)重载是否违反 LSP?

答案取决于语言。

在典型的强类型 OO 语言中,如 Java、C++、C# 等,当您编写方法调用时,方法调用(如a.func(b,c)接收类型(本例中为a的类型),以及arguments的数量和类型。

在这样的语言中,具有不同数量的 arguments 或不同类型的 arguments 的方法是完全不同的方法。 拥有不同数量的 arguments 就像拥有不同的名称一样。 当您“重载”一个方法时,就像创建一个具有不同名称的方法一样,因此您不会通过从基础 class 重载一个方法来违反 LSP。

不过,您的问题似乎是关于 python 的,并且在典型的动态类型语言(如 python、JavaScript 等)中,当您编写像a.func(b,c)这样的方法调用时,只查找调用方法, (在与接收对象关联的表中查找)。 在这样的语言中,没有方法或函数的重载

在您的示例中,您已经用两个参数 function覆盖func的单参数定义。 这意味着您派生的 class 的消费者不能再使用一个参数调用func ,这确实违反了 LSP。

在现实生活中,合同是甲方和乙方对彼此的期望,即他们提供的保证。 编程相同:

  • 这个方法接受什么范围的arguments?
  • 您可以从中期待什么行为?
  • 什么时候抛出错误?

它是方法的接口和文档的组合。

当我们说一个孩子应该遵循相同的契约时,我们的意思是,如果我们用另一个 ChildC 替换实现,那么将遵循与父级中描述的相同的约束,相同的行为。

例如,在 Java 中,我们有 Map 的概念(类似于 Python 中的 Dict),并且有 2 个不同的实现:HashMap 和 LinkedMap。 一个不保留加法的顺序,另一个 - 保留。 但是在合同中(在界面及其文档中)没有人说应该保留订单。 因此,如果我们的代码使用 HashMap 然后我们用 LinkedHashMap 替换它 - 它仍然可以完成父文档和签名所需的一切。

还有另一个实现:ConcurrentHashMap。 现在,它违反了 LSP。 因为 HashMap,LinkedHashMap 可以有 null 键,而 ConcurrentHashMap 不能。 因此,我们不能只用另一种实现替换一个实现——如果客户端实际上将 null 放在那里,它可能会破坏事情。

Map 的文档缓解了该问题,该文档称某些实现可能不接受 null,但这更像是一种 hack。

至于重载-您正在创建一个具有相同名称的单独方法,它不必遵循第一个合同。 这些方法可能根本没有共同之处。 尽管如其他答案中所述 - Python 中没有方法重载,但您只是用新方法替换旧方法,因此在子类中完成时这是一种覆盖。

PS:如果您使用策略或命令 - 实施会完全不同。 所以 LSP 在这里不适用。 但是他们仍然需要遵循父级中描述的通用合同。

要理解 LSP 的含义,口号“不再要求,promise 也不少”有很大帮助。 覆盖方法不能要求更多的 arguments 或更多特定类型的 arguments(即,它们必须是逆变的)。 它必须 promise 返回与被覆盖方法相同或更具体的类型的值(即,它必须是协变的)。

语义约束是覆盖方法必须在子类中执行与在超类中执行的“相同的事情”。 这种做同样事情的概念不容易形式化,它取决于类本身的含义。 但是,如果覆盖方法调用了被覆盖的方法,这是语义一致性的强烈标志。

重载与 LSP 无关。 这只是允许不同方法具有相同名称的编译时技巧。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM