简体   繁体   English

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

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

I've been digging into some of the SOLID design principles lately, and some information I got from one source originally made sense to me, but based on the strict definitions I've been able to find for thr LSP, it seems like that info might not be correct. 我最近一直在深入研究一些SOLID设计原则,我从一个来源获得的一些信息最初对我有意义,但基于我已经能够找到的针对thr LSP的严格定义,它似乎就是这样的信息可能不正确。 The info was specifically that: 信息具体是:

1) Not calling back into super() on an overridden method violates LSP (or at least opens you up to violation) because the base class's behavior may change at some point, and your subclass could be missing that behavior in a way that causes the subclass to no longer be substitutable for the parent. 1)在重写方法上没有回调到super()会违反LSP(或者至少会打开你的违规行为),因为基类的行为可能会在某些时候发生变化,而你的子类可能会以导致子类不再可替代父类。 That seems to make sense to me, but it would be great if someone could elaborate on that/give some info on when it would ever be appropriate to not call back into super. 这对我来说似乎有意义,但是如果有人可以详细说明/给出一些关于什么时候不适合不回电话的信息那就太棒了。

2) Subclasses should not be less restrictive than the parent. 2)子类的限制不应低于父类。 The example was: if you have a parent class that takes only positive integers, then you make a subclass that accepts both positive and negative ints. 示例是:如果您的父类只接受正整数,那么您创建一个接受正负int的子类。 So the child should work fine in place of the parent, but the child can't delegate back to super in this case. 因此,孩子应该在父母的位置上工作正常,但在这种情况下,孩子不能委托给超级。

I thought that made sense, but the info on LSP seems to say the opposite: a precondition can't be strengthened by the child. 我认为这是有道理的,但LSP上的信息似乎恰恰相反:孩子无法强化前提条件。 Both seem to make sense to me, but Liskov only states that the precondition can't be strengthened and the postcondition can't be weakened. 两者似乎都对我有意义,但利斯科夫只是说前提条件不能加强,后置条件不能削弱。 Can someone help enlighten me on this? 有人可以帮助我这个吗?

1) Case, when it would ever be appropriate to not call back into super 1)情况,当不适合不回电话时

Usually (but not always) such case means something is wrong with the class hierarchy design. 通常(但并非总是)这种情况意味着类层次结构设计出了问题。

Not calling the super class implementation doesn't violate the LSP if the child method accepts correct input types and returns correct output type. 如果子方法接受正确的输入类型并返回正确的输出类型,则不调用超类实现不会违反LSP。 It only indicates the possibility of the problem. 它只表明问题的可能性。

Here is an absolutely valid example when you don't call the super method: 当你不调用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

Here the CatOwner::findAPet() returns a Cat (an Animal subclass), this is valid in terms of LSP and we don't call the parent implementation. 这里CatOwner::findAPet()返回一个Cat (一个Animal子类),这在LSP方面是有效的,我们不调用父实现。

Take into account that calling the parent implementation does not guarantee that you can not fall into the same problem as when you don't call it. 请注意,调用父实现并不能保证您不会遇到与不调用它时相同的问题。

Consider this example: 考虑这个例子:

 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) "Preconditions cannot be strengthened in a subtype". 2)“在子类型中不能加强先决条件”。 This also means that pre-conditions (expectations related to input parameters) can stay the same or be weakened. 这也意味着前提条件(与输入参数相关的期望)可以保持不变或被削弱。 In the example you mention, the pre-conditions are actually weakened, so there is no conflict: 在你提到的例子中,前提条件实际上被削弱了,所以没有冲突:

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

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

What is not exactly correct is the first sentence: "Subclasses should not be less restrictive than the parent". 不正确的是第一句话:“子类不应该比父母更少限制”。 Subclasses can be less restrictive when we talk about pre-conditions (input parameters) and this is what shown in the example. 当我们讨论前置条件(输入参数)时,子类可以减少限制,这就是示例中显示的内容。

The Liskov substitution principle requires that you can use a subtype when the base type is expected. Liskov替换原则要求您可以在期望基类型时使用子类型。 To do this you must adhere to the contract of the base type. 为此,您必须遵守基本类型的合同。 For a base-class B with a method f, precondition Pre, postcondition Post and invariant I, this means that 对于具有方法f,前提条件Pre,postcondition Post和不变I的基类B,这意味着

  • The caller code only guarantees that Pre holds when potentially using your object. 调用者代码仅保证Pre在可能使用您的对象时保留。 If Pre holds, your precondition must also hold. 如果Pre持有,你的先决条件也必须成立。 Logically Pre implies the pre-condition of your derived method. Logically Pre意味着派生方法的前提条件。 This means that you can widen the pre-condition because Pre -> (Pre and condition), as long as there is no contradiction 这意味着你可以扩大前置条件,因为Pre - >(Pre和condition),只要没有矛盾
  • The caller code expects that Post holds when the method is finished. 调用者代码期望Post在方法完成时保留。 This means that you can add more guarantees, but you shall not weaken the Postcondition. 这意味着您可以添加更多保证,但不得削弱后置条件。
  • The invariant of your sub-class must be compatible with the base-class' invariant. 子类的不变量必须与基类的不变量兼容。

I would consider explicitly calling base-class implementation a code smell (except for constructors). 我会考虑显式调用基类实现代码气味(构造函数除外)。 It is much better to use the te,plate method pattern (or non-virtual interface in C++ ) to enforce the base-class contract. 使用te,plate方法模式 (或C ++中的非虚拟接口 )来强制执行基类合约要好得多。 In Python, this looks like: 在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