繁体   English   中英

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

[英]Does omitting super() and/or *weakening* preconditions violate the Liskov Substitution Principle?

我最近一直在深入研究一些SOLID设计原则,我从一个来源获得的一些信息最初对我有意义,但基于我已经能够找到的针对thr LSP的严格定义,它似乎就是这样的信息可能不正确。 信息具体是:

1)在重写方法上没有回调到super()会违反LSP(或者至少会打开你的违规行为),因为基类的行为可能会在某些时候发生变化,而你的子类可能会以导致子类不再可替代父类。 这对我来说似乎有意义,但是如果有人可以详细说明/给出一些关于什么时候不适合不回电话的信息那就太棒了。

2)子类的限制不应低于父类。 示例是:如果您的父类只接受正整数,那么您创建一个接受正负int的子类。 因此,孩子应该在父母的位置上工作正常,但在这种情况下,孩子不能委托给超级。

我认为这是有道理的,但LSP上的信息似乎恰恰相反:孩子无法强化前提条件。 两者似乎都对我有意义,但利斯科夫只是说前提条件不能加强,后置条件不能削弱。 有人可以帮助我这个吗?

1)情况,当不适合不回电话时

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

如果子方法接受正确的输入类型并返回正确的输出类型,则不调用超类实现不会违反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)

不正确的是第一句话:“子类不应该比父母更少限制”。 当我们讨论前置条件(输入参数)时,子类可以减少限制,这就是示例中显示的内容。

Liskov替换原则要求您可以在期望基类型时使用子类型。 为此,您必须遵守基本类型的合同。 对于具有方法f,前提条件Pre,postcondition Post和不变I的基类B,这意味着

  • 调用者代码仅保证Pre在可能使用您的对象时保留。 如果Pre持有,你的先决条件也必须成立。 Logically Pre意味着派生方法的前提条件。 这意味着你可以扩大前置条件,因为Pre - >(Pre和condition),只要没有矛盾
  • 调用者代码期望Post在方法完成时保留。 这意味着您可以添加更多保证,但不得削弱后置条件。
  • 子类的不变量必须与基类的不变量兼容。

我会考虑显式调用基类实现代码气味(构造函数除外)。 使用te,plate方法模式 (或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

暂无
暂无

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

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