Search code examples
oopinheritancesolid-principlesdesign-principlesliskov-substitution-principle

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. 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. 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. 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. 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. 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?


Solution

  • 1) Case, when it would ever be appropriate to not call back into super

    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. It only indicates the possibility of the problem.

    Here is an absolutely valid example when you don't call the super method:

    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.

    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". 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.