当前位置: 首页 > 知识库问答 >
问题:

省略super()和/或*弱化*先决条件是否违反Liskov替换原则?

束福
2023-03-14

我最近一直在深入研究一些坚实的设计原则,我从一个来源获得的一些信息最初对我来说是有意义的,但基于我能够找到的关于thr LSP的严格定义,这些信息似乎不正确。具体信息如下:

1) 不在重写的方法上回调super()会违反LSP(或者至少会导致违反),因为基类的行为可能会在某个点上发生更改,并且您的子类可能会丢失该行为,从而导致子类不再可以替换父类。这对我来说似乎是有道理的,但如果有人能详细说明这一点/提供一些信息,说明什么时候不回super是合适的,那就太好了。

2) 子类的限制性不应小于父类。这个例子是:如果您有一个只接受正整数的父类,那么您可以创建一个同时接受正整数和负整数的子类。因此,孩子应该代替父母工作得很好,但在这种情况下,孩子不能委托给super。

我认为这是有道理的,但是LSP上的信息似乎恰恰相反:一个先决条件不能被孩子强化。两者对我来说似乎都有意义,但利斯科夫只是说先决条件不能加强,后置不能削弱。有人能帮我解释一下吗?

共有2个答案

凤修为
2023-03-14

Liskov替换原则要求在需要基类型时可以使用子类型。为此,必须遵守基本类型的约定。对于具有方法f、前置条件、后置条件和不变量I的基类B,这意味着

  • 调用方代码仅保证在可能使用对象时预保持。如果Pre保持不变,则前提条件也必须保持不变。逻辑上,Pre表示派生方法的前置条件。这意味着您可以扩大前提条件,因为前提条件-

我会考虑显式地将基类实现称为代码气味(构造函数除外)。使用te、板方法模式(或C中的非虚拟接口)来强制执行基类契约要好得多。在Python中,这看起来像:

class Base:
   def publicMethod(self, x):
       // do something
       self.templateMethod(x)
       // do something else

   def templateMethod(self, x):
       // has to be overriden by sub-classes
       // it provides extension hooks, but
       // publicMethod ensures that the overall
       // functionality is correct
赵刚豪
2023-03-14

1) 在这种情况下,什么时候不回电话给super是合适的

通常(但并非总是)这种情况意味着类层次结构设计有问题。

如果子方法接受正确的输入类型并返回正确的输出类型,则不调用超类实现不会违反LSP。它只表明问题的可能性。

下面是一个不调用super方法的绝对有效的示例:

class Animal

   void eat(Food food)
       // Eat the food

class Cat extends Animal

   void meow()
       // Say meow


class AnimalOwner

   Animal findAPet() 
       return new Animal()

class CatOwner

   // we can return the subclass of Animal here
   Cat findAPet() 
       return new Cat() // We don't need to use the parent implementation

这里的CatOwner::findAPet()返回一个Cat(一个Animal子类),这在LSP方面是有效的,我们不调用父实现。

考虑到调用父实现并不能保证您不会陷入与不调用它时相同的问题。

考虑这个例子:

 class Child

    Output doSomething()
        parent = super::doSomething()
        if parent->isNotGood():
           return new OutputSubclass()  // We called super, but we return 
                                        // something different, might be not safe
        else:
           return parent                // We called super and return 
                                        // the same value, safe

2)“前提条件不能在一个子类型中加强”。这也意味着前置条件(与输入参数相关的期望)可以保持不变或被削弱。在你提到的例子中,前提条件实际上被削弱了,所以没有冲突:

Parent::doSomething(PositiveInteger value)  // Only positive integers

Child::doSomething(Integer value)           // Positive or negative integers, 
                                            // pre-condition is weaker 
                                            // (covers wider area of values)

不完全正确的是第一句话:“子类不应该比父类限制更少”。当我们谈论前置条件(输入参数)时,子类的限制性较小,这就是示例中所示的。

 类似资料:
  • 我是OOP的新手。最近我读到了关于Liskov替换原理的文章。 在下面给出的代码中,Square类继承Give_区域。假设Square类与Square相关(比如有效性检查)。Give_Area给出正方形的面积(4个顶点位于圆的周长上)和圆的面积。所以,如果给我一个半径,我必须打印圆和正方形的面积(由放置在圆周长上的顶点组成)。为了得到圆的面积,我使用了一个参数。但在求平方面积时没有参数。因此,我在

  • 来自维基百科, Liskov的行为子类型概念定义了对象的可替代性概念;也就是说,如果S是T的子类型,则程序中T类型的对象可以替换为S类型的对象,而不改变该程序的任何期望属性(例如正确性)。 假设以下类层次结构: 基本抽象类-。它有一个只读属性,在后继程序中被重写。 基类的继承者-,它重写并返回灰色。 Cat的继任者-,它覆盖并返回带条纹的。 然后我们声明一个方法,参数类型为(不是)。 向该方法发送

  • 我正在阅读为什么Java中的数组协方差不好(为什么数组是协方差的,而泛型是不变的?)。如果是的子类型,则是的子类型。这是一个问题,因为这样的事情是可以做的: 这与“正确”实现的泛型不同。不是的子类型 我试图理解为什么它是坏的本质,并且刚刚读了关于LSP的文章。它有没有违反LSP?似乎没有明显的违规行为。

  • 如果S是T的一个子类型,那么T类型的对象可以被S类型的对象替换。 子类有两种不同的行为(选中与未选中),在某些情况下,除非更改当前代码,否则无法用子类对象有效地替换基类用法,例如,如果编写如下代码: 这是违反吗?,为什么/为什么不?。 资料来源:http://www.oracle.com/technetwork/articles/entarch/effective-exceptions-09234

  • Liskov替换原则指出,您应该编写您的类继承,这样将子类型交换为它们的基类型就不会改变应用程序的行为。 然而,虚拟关键字字面上似乎存在,以允许子类型的行为不同于基类型。虚拟/覆盖关键字(不包括覆盖抽象成员)的大多数使用不可能违反Liskov吗?我觉得这可能比我理解的更微妙。也许这是一个“规则有时会被打破”的情况,或者原则中的“不要有不同的行为”部分有一个灰色地带。

  • 我刚刚安装了Microsoft代码合同。这是的一部分。NET框架和Visual Studio加载项。它提供运行时检查和定义的合同的静态检查。 该工具有四个警告级别,因此我设置了最高级别。 我已经声明了违反Liskov替换原则的类。 LSP规定: 如果S是T的子类型,则T类型的对象可以替换为S类型的对象,而不改变该程序的任何期望属性 在我的例子中,违规行为是这样的:人=新孩子(23);。我们应该能够