里斯科夫替代原则与内省或鸭类打字不相容吗?


11

我是否正确理解在对象可以自我检查的语言(如鸭式语言中常见的语言)中不能遵守《里斯科夫替代原理》

例如,在Ruby中,如果一个类B从一个类继承A,然后为每个对象xAx.class是要回报A的,但如果x是一个对象Bx.class是不会返回A

这是LSP的声明:

q(x)是关于类型T的对象x的可证明性质。那么,对于类型S的对象yq(y)应该是可证明的,其中ST的子类型。

以Ruby为例,

class T; end
class S < T; end

如属性q(x) =所示,以这种形式违反LSPx.class.name == 'T'


加成。如果答案是“是”(与内省不兼容的LSP),那么我的另一个问题是:是否存在某种修改后的“弱”形式的LSP,它可能适用于动态语言,可能在某些附加条件下并且仅具有特殊类型的性质


更新。作为参考,这是我在网络上发现的另一种LSP公式:

使用指针或对基类的引用的函数必须能够使用派生类的对象,而无需了解它。

还有一个:

如果S是声明的T的子类型,则将S类型的对象视为T类型的对象,如果将它们视为T类型的对象,则它们的行为也应与预期的T类型的对象相同。

最后一个注释为:

请注意,LSP全部关于对象的预期行为。只有清楚知道对象的预期行为后,才能遵循LSP。

这似乎比最初的要弱,并且可能可以观察到,但是我希望看到它正式化,尤其要解释谁决定预期的行为是什么。

那么,LSP是否不是编程语言中的一对类的属性,而是祖先类满足的一对类以及给定的一组属性?实际上,这是否意味着要构造一个尊重LSP的子类(后代类),必须知道祖先类的所有可能用法?根据LSP,祖先类应该可以用任何后代类替换,对吗?


更新。 我已经接受了答案,但是我想添加一个来自Ruby的更具体的例子来说明这个问题。在Ruby中,每个类都是模块,从某种意义上来说,Class类是类的后代Module。然而:

class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module

module M; end
M.class # => Module

o = Object.new

o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)

2
几乎所有现代语言都提供了一定程度的自省,因此问题并不是真正针对Ruby的。
约阿希姆·绍尔

我了解,我以Ruby为例。我不知道,也许在其他一些具有内省性的语言中,LSP有一些“弱形式”,但是,如果我正确理解该原理,它与内省不兼容。
阿列克谢2012年

我从标题中删除了“ Ruby”。
阿列克谢2012年

2
简短的答案是它们是兼容的。这是我最同意的博客文章:鸭打字的Liskov替代原理
K.Steff

2
@Alexey属性在此上下文中是对象的不变量。例如,不可变对象具有其值不变的属性。如果您查看好的单元测试,则它们应该准确测试这些属性。
K.Steff

Answers:


29

这是实际原理

q(x)一个关于x类型为的对象可证明的属性T。然后q(y)应该是可证明的对象y类型的S,其中S是的子类型T

以及出色的维基百科摘要:

它指出,在计算机程序中,如果S是T的子类型,则可以用类型S的对象替换类型T的对象(即,类型S的对象可以替换类型T的对象),而无需更改任何该程序的理想属性(正确性,执行的任务等)。

以及本文中的一些相关报价:

现在需要的是一个更严格的要求来约束子类型的行为:即使对象实际上是该类型的子类型的成员,也可以使用对象的假定类型的规范来证明其属性。

类型规范包括以下信息:
-类型名称;
-类型值空间的描述;
-对于每种类型的方法:
---名称;
---其签名(包括信号异常);
---根据前置条件和后置条件的行为。

接下来的问题:

我是否正确理解在对象可以自我检查的语言(如鸭式语言中常见的语言)中不能遵守《里斯科夫替代原理》?

没有。

A.class返回一个类。
B.class返回一个类。

由于可以在更具体的类型上进行相同的调用并获得兼容的结果,因此LSP成立。问题在于,使用动态语言,您仍然可以在结果上调用事物,并期望它们在那里。

但是,让我们考虑一种静态的,结构化的(鸭子)类型的语言。在这种情况下,A.class将返回一个约束,该约束必须是A类型,或者是的子类型A。这提供了静态保证,即的任何子类型都A必须提供一种方法,T.class其结果是满足该约束的类型。

这提供了一个更强有力的断言,即LSP在支持鸭子类型的语言中保持有效,并且在Ruby之类的LSP中违反LSP的行为更多是由于正常的动态误用而不是语言设计不兼容引起的。


1
“由于可以在更具体的类型上进行相同的调用并获得兼容的结果,因此LSP成立”。如果我理解正确,则LSP保持一致。相对于给定的约束,也许可以有某种“周”形式的LSP,而不是要求所有属性仅满足给定的约束。无论如何,我将不胜感激。
阿列克谢2012年

@Alexey编辑为包含LSP的含义。如果我可以在期望A的地方使用B,则LSP成立。我很好奇您认为Ruby的.class可能违反了该规定。
Telastyn

3
@Alexey-如果您的程序包含fail unless x.foo == 42并且一个子类型返回0,那是相同的。这不是LSP的故障,而是程序的正常运行。多态性并不违反LSP。
Telastyn 2012年

1
@Alexey-好的。假设它是一个属性。在那种情况下,您的代码确实违反了LSP,因为它不允许子类型具有相同的语义行为。但这对动态语言或鸭子类型的语言并不是特别的特殊。他们在语言设计中没有做任何导致违规的事情。您编写的代码可以。请记住,LSP是程序设计的原则(因此,定义为),而不是程序的数学特性。
Telastyn

6
@Alexey:如果您编写的内容依赖于x.class == A,那么违反LSP的是您的代码,而不是语言。几乎每种编程语言都可能编写违反LSP的代码。
安德列斯·F

7

在LSP的上下文中,“属性”是可以在类型(或对象)上观察到的东西。特别是谈到“可证明的财产”。

这样的“属性”可能存在foo()没有返回值(并遵循其文档中设置的合同)的方法。

确保不要将此术语与“属性”混淆,因为“ class是Ruby中每个对象的属性”。这样的“属性”可以是“ LSP属性”,但并非自动相同!

现在,问题的答案在很大程度上取决于您对“属性”的定义有多严格。如果您说“类的属性A.class将返回对象的类型”,则B实际上确实具有该属性。

但是,如果您定义的“财产”是“ .class回报A”,那显然B不会有属性。

但是,第二个定义不是很有用,因为您实际上已经找到了一种声明常量的循环方法。


我只能想到一个程序“属性”的定义:对于给定的输入,它返回给定的值,或更普遍地,当在另一个程序中用作块时,另一个程序的给定输入将返回一个给定的值。给定值。有了这个定义,我看不到“ .class将返回对象的类型”的含义。如果这意味着x.class == x.class,这不是一个有趣的属性。
阿列克谢2012年

1
@Alexey:我更新了我的问题,澄清了在LSP上下文中“属性”的含义。
约阿希姆·绍尔

2
@Alexey:调查文件没有找到具体的定义或“属性”。这可能是因为该术语在CS的一般意义上是“可以观察到/证实的某些物体”。它与另一个“对象的字段” 无关
约阿希姆·绍尔

4
@Alexey:我不知道还能告诉你什么。我使用“属性是对象的某种品质或属性”的定义。“色”是一个性质物理的,可见的对象。“密度”是材料的属性。“具有指定方法”是一个属性类/对象。
约阿希姆·绍尔

4
@Alexey:我想您是在向婴儿扔水:仅仅因为某些属性无法保留LSP并不意味着它没有用,或者“不保留任何语言”。但是,这种讨论将在这里进行得很远。
约阿希姆·绍尔

5

据我了解,内省与LSP不兼容。基本上,只要一个对象支持与另一个对象相同的方法,则这两个对象应该是可互换的。也就是说,如果你的代码需要一个Address对象,那么它并不重要,如果它是一个CustomerAddress或一WarehouseAddress,只要双方提供(例如)getStreetAddress()getCityName()getRegion()getPostalCode()。您当然可以创建某种装饰器,该装饰器采用不同类型的对象并使用自省来提供所需的方法(例如,一个DestinationAddress类采用一个Shipment对象并将送货地址显示为Address),但这不是必需的,并且肯定会不会阻止LSP的应用。


2
@Alexey:如果对象支持相同的方法,则它们是“相同的”。这意味着相同的名称,相同的参数数量和类型,相同的返回类型和相同的副作用(只要它们对调用代码可见)。这些方法的行为可能完全不同,但是只要遵守合同,就可以。
TMN 2012年

1
@Alexey:但是我为什么要签订这样的合同?该合同有什么实际用途?如果我有这样的合同,我可以简单地替换的每一次出现x.class.name'A' ,有效地使x.class.name 无用
约阿希姆·绍尔

1
@Alexey:再说一次:仅仅因为您可以定义一个不能通过扩展另一个类来实现的合同就不会破坏LSP。这只是意味着您已经建立了一个不可扩展的类。如果我定义一种方法“ 如果提供的代码块在有限的时间内结束,则返回”,那么我也有一个无法履行的合同。这并不意味着编程是没有用的。
约阿希姆·绍尔

2
@Alexey试图确定x.class.name == 'A'鸭子类型是否是反模式:毕竟,鸭子类型来自“如果它像鸭子一样嘎嘎地走,那就是鸭子”。因此,如果它的行为A与所执行的合同相似并且能够AA
兑现

1
@Alexey为您提供了清晰的定义。对象的类不是其行为或契约或任何您想调用的对象的一部分。您正在用“对象字段,例如x.myField”来模糊“属性”,正如已经指出的那样,它是不同的。在这种情况下,属性更像是数学属性,例如类型不变量。此外,如果您想输入鸭子,这是一种检查确切类型的反模式。那么,您的LSP和鸭子输入又有什么问题呢?;)
Andres F.

4

浏览了Barbara Liskov的原始论文之后,我发现了如何完成Wikipedia的定义,以便LSP几乎可以用任何一种语言来满足。

首先,“可证明”一词在定义中很重要。Wikipedia文章中未对此进行说明,并且在其他地方未提及“约束”。

这是论文的第一个重要报价:

所需要的是一个更强的要求来约束子类型的行为:即使对象实际上是该类型的子类型的成员,也可以 使用 对象的假定类型的规范来证明的属性应保持……

这是第二篇,解释什么是类型规范

类型规范包含以下信息:

  • 类型名称;
  • 类型值空间的描述;
  • 对于每种类型的方法:
    • 其名称;
    • 它的签名(包括发出信号的异常);
    • 它在先决条件和后置条件方面的行为。

因此,LSP仅对于给定的类型规范有意义,对于适当的类型规范(例如,对于空类型而言),可以用任何一种语言来满足。

我认为Telastyn的答案与我寻找的答案最接近,因为明确提到了“约束”。


Telastyn,如果您可以将这些引号添加到您的答案中,我宁愿接受您的而不是我自己的。
阿列克谢2012年

2
标记并不完全相同,我改变了一点,但包括了引号。
Telastyn

抱歉,Joachim Sauer已经提到了您不喜欢的“可证明”属性。通常,您只是重申了现有答案。老实说,我不知道您在寻找什么...
Andres F.

不,它没有解释为“可以从哪里证明?”。如果您允许太多知识,则该属性x.class.name = 'A'对于所有x班级都是可证明的A。该类型规范没有定义,并与LSP其确切的关系是既没有非正式虽然一些迹象表明给予。我已经在Liskov的论文中找到了想要的内容,并在上面回答了我的问题。
阿列克谢2012年

我认为您大胆的话是关键。如果超类型记录了对于该类型中的任何x一个类型,x.woozle将产生yield undefined,那么没有x.woozle不屈服的任何类型都不undefined是适当的子类型。如果超类型未记录任何有关的信息x.woozle,那么在超类型上使用x.woozle会产生屈服的事实undefined并不意味着对子类型可能执行的操作。
超级猫

3

引用维基百科有关LSP的文章,“可替代性是面向对象编程中的一项原则”。这是程序设计的原则和一部分。如果编写依赖的代码x.class == A,那么违反LSP的就是您的代码。请注意,这种破损代码在Java中也是可能的,无需输入鸭子。

鸭子类型中的任何内容都不会破​​坏LSP。仅当您滥用它时(如您的示例所示)。

额外的想法:仍然不明确地检查对象的类是否达到了鸭子输入的目的?


安德列斯,能否请您给出LSP的定义?
阿列克谢(Alexey)2012年

1
@Alexey Wikipedia中根据子类型对LSP进行了精确定义。非正式定义是Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).。对象的确切类别不是“程序的理想属性”之一;否则,它将不仅与鸭子类型相反,而且与通常包括Java风格的子类型相反。
安德列斯·F

2
@Alexey还请注意,您的示例x.class == A违反了LSP 鸭子类型。如果要检查实际类型,则没有必要使用鸭子类型。
安德列斯·F。

安德列斯,这个定义不够精确,我无法理解。哪个程序,给定的程序或任何程序?什么是理想属性?如果该类在库中,则不同的应用程序可能会认为需要不同的属性。我看不到代码行如何违反LSP,因为我认为LSP是给定编程语言中一对类的属性:(AB)是否满足LSP。如果LSP依赖于其他地方使用的代码,则不会解释允许使用什么代码。我希望在这里找到一些东西: cse.ohio-state.edu/~neelam/courses/788/lwb.pdf
Alexey,

2
@Alexey LSP保留(或不保留)特定设计。这是在设计中寻找的东西;它通常不是语言的属性。它没有比实际定义更精确的信息:Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T。显然,这x.class不是这里有趣的属性之一;否则,Java的多态性也不起作用。有没有什么内在的鸭打字你的“x.class问题”。到目前为止,您是否同意?
安德列斯·F
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.