重载(而非重写)是否违反了Liskov替代原则?

2024-06-09 14:18:48 发布

您现在位置:Python中文网/ 问答频道 /正文

关于LSP的讨论有很多,但似乎都过于模糊。
首先,LSP指出,要正确地覆盖子类中的超类方法(而不是重载),应该确保子类方法:

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

通过semantic meaning我的意思是,如果基类方法意味着它返回int,而这个int意味着,比如说,USD或EUR,那么重载方法也应该意味着返回的值是USD或EUR,即使在一种情况下返回“RUB”也会违反LSP

但是,如果我的基类看起来像这样(示例是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. 使用基类中不存在的签名(在参数列表的一部分)重载是否违反LSP

Tags: 方法语言类型return语义情况eur基类
3条回答

在现实生活中,合同是甲、乙双方可以相互期望的,他们提供的保证。在编程中也是如此:

  • 此方法接受什么范围的参数
  • 你能从中期待什么样的行为
  • in何时抛出错误

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

当我们说一个孩子应该遵循相同的契约时,我们的意思是,如果我们用另一个孩子C替换实现,那么就会遵循相同的约束,就像在父对象中描述的一样,相同的行为

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

还有另一个实现:ConcurrentHashMap。既然有人违反了LSP。因为HashMap,LinkedHashMap可以有空键,而ConcurrentHashMap不能。所以,我们不能仅仅用另一个实现替换一个实现——如果客户机真的在那个里放置null,它可能会破坏一些东西

Map文档指出,一些实现可能不接受null,但这更像是一种黑客行为,这缓解了这个问题

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

PS:如果你使用一个策略或命令,那么实现将是完全不同的事情。所以LSP在这里不适用。但他们仍然需要遵循父级中描述的通用合同

为了理解LSP的含义,标语“不再要求,不再承诺””帮助很大。重写方法不能要求更多的参数或更特定类型的参数(即,它们必须是逆变的)。它必须承诺返回与重写方法相同或更具体类型的值(即,它必须是协变的)

语义约束是重写方法必须在子类中执行与在超类中相同的操作。这种做同样事情的概念不容易形式化,并且取决于类本身的含义。但是,如果重写方法调用被重写的方法,这是语义一致性的有力标志

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

答案取决于语言

典型的类式OO语言,如java、C++、C等,当你写一个方法调用,比如^ {CD1>}时,实际方法调用是基于方法名称来确定的,以及接收类型(在这个例子中是^ {CD2>}),以及参数的数量和类型。p>

在这样的语言中,具有不同数量参数或不同类型参数的方法是完全不同的方法。拥有不同数量的参数就像拥有不同的名称一样。当您“重载”一个方法时,这就像使用不同的名称创建一个方法,所以您不会因为重载基类中的方法而违反LSP

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

在您的示例中,使用双参数函数覆盖了func的单参数定义。这意味着派生类的使用者不能再使用一个参数调用func,这确实是LSP冲突

相关问题 更多 >