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

时间:2016-01-28 20:01:24

标签: oop inheritance solid-principles design-principles liskov-substitution-principle

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

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

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

我认为这是有道理的,但LSP上的信息似乎反过来说:孩子可以强化的前提条件。两者似乎都对我有意义,但利斯科夫只表示前提条件不能得到加强,后置条件不能被削弱。有人可以帮我启发吗?

2 个答案:

答案 0 :(得分:1)

1)案例,如果不适合不回电话

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

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

当你不调用超级方法时,这是一个绝对有效的例子:

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()返回CatAnimal子类),这在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)

第一句话不完全正确:"子类的限制性不应低于父级"。 当我们讨论前置条件(输入参数)时,子类可以减少限制,这就是示例中所示。

答案 1 :(得分:0)

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

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

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