3

Learning about the Liskov principle, I do understand that preconditions can be weakened and postconditions can be stronger in subtypes. However, I do not understand how invariants can be stronger in the subtype? If the base class says e.g. the Speed must be lower than 100 al the time, and then a subtype says Speed < 50, replacing the base with the subtype can cause some issues, or not? I mean, if somewhere the code works with the base class and works the speed of 90, introducing the subtype means it will be invalid.

Code and some text can be found in the slides 15=16:

https://www.cs.cmu.edu/~aldrich/214/slides/formal-analysis-part2.pdf

  • 1
    Is speed an input or an output in your exampe? – Jacob Raihle Jan 26 '18 at 9:35
  • @JacobRaihle It is the invariant of the class. – Ezoela Vacca Jan 26 '18 at 9:40
  • Never use inheritance to set a scalar value like a maximum speed. If you must extend a class, use it to extend the behaviour, not modify a number. – Mr Cochese Jan 26 '18 at 10:25
  • 1
    But where is speed coming from? Is it provided, and the type promises to accept values up to 100, or does the type accept other input and promise a behavior based on a speed no greater than 100? I am getting mixed messages from your question and comments here. – Jacob Raihle Jan 26 '18 at 10:48
  • 1
    @guillaume31 That's kind of irrelevant. The maximum speed is still logically a property of the base class, whether extended behaviours use it or not. If you extend a class purely to change a number, you're tying yourself in knots. – Mr Cochese Jan 26 '18 at 11:20
2

Liskov Substitution Principle states that you can replace implementation of class T without altering any of the desirable properties of T. In your example you mention a base class where the Speed must always be lower than 100 and in an implementation, the Speed must always be lower than 50.

This isn't a violation because the base class only requires that speed be lower than 100 and that is presumably met regardless of if your derived class requires it to be lower than 50 or if it requires it to be lower than 75.

In other words if speed is lower than 50, it must also therefore be lower than 100 and so there is no violation. The violation would be if your derived class allowed speed to exceed 100.

Replacing one instance where speed is lower than 100 with another that allows speed to be over 200 would result in breaking Liskov Substitution Principle as well as the laws of the road!

  • I am still thinking about the implementation - when I have a code working with the base class, let's assume it provides such values that the invariant is 90. Now I replace the base class with the subclass and keep the rest unchanged...and 90 is not acceptable anymore. – Ezoela Vacca Jan 26 '18 at 10:33
  • @EzoelaVacca The 90 would be the conditions of the base class. If that is violated, even indirectly, then it no longer can be substituted without potentially undesireable side effects. Not sure I understand where you're coming from. – Neil Jan 26 '18 at 10:45
  • As it is the invariant, let's say it is calculated based on some constructor parameters, like X*Y. So you pass 10 and 9 to the base class object constructor, so its invariant is 90, that is correct. Now you replace the base class with its subclass, and 90 becomes invalid because the invariant should be less than 50. – Ezoela Vacca Jan 26 '18 at 10:55
  • Well, in that case, you can't say that an invariant is defined in terms of a specific number (because it has to apply to the whole type, i.e. not just to a specific instance, but to every instance), but you can say that the invariant is that the speed is lower than X*Y. Note that when the subtype is instantiated, you have to provide these parameters as well. – Filip Milovanović Jan 26 '18 at 11:10
  • 1
    @EzoelaVacca If the derived class states that the values X*Y must be less than 50 and X*Y (due to values in the base class) end up being 90, your implementation is violating its own prerequisite. Liskov Substitution Principle only says that whereever you use the base class, you use an implementation that guarantees speed is less than 90. If the implementation doesn't meet its own prerequisite of less than 50, this isn't a violation of Liskov. It's a separate issue. – Neil Jan 26 '18 at 11:38
2

Liskov substitutability does not mean that you can substitute an object of a subclass for an object of a superclass and your program will still be correct. The only thing it guarantees is that promises made by the superclass will be honored by all subclasses.

In your case, the superclass promised "My speed will not exceed 100", and it keeps that promise. The subclass also keeps this promise.

The subclass also introduces a new, stricter promise: "My speed will not exceed 50", and it honors this promise as well. This is possible because the states allowed by the new premise are a strict subset of the states allowed by the old promise.

The superclass doesn't honor this new promise, in fact it doesn't even know about it. That means that its behaviour will be different from the behaviour of the subclass. Obviously, this can affect program correctness - substitutability does not mean that any of the classes in a class hierarchy is as good as the other.

Then what good is Liskov substitutability? It's only as good as the type system in your language. It does a good job enforcing pre- and postconditions that the type system in your language can express. For instance, once you declare a method that returns an unsigned int, you can be sure that it will never return a negative value, and neither will any subclass. But you probably cannot express "this method finds the best nearest-neighbour classification of the input values" in the type system, therefore the LSP cannot guarantee program correctness under inheritance. (To be fair, almost no formalism can.)

  • 1
    Actually, the definition does say that - that the behavior, correctness (and that is explicitly mentioned) does not change. – Ezoela Vacca Jan 26 '18 at 9:15
  • @EzoelaVacca The definition says that correctness according to the pre- and postconditions does not change. But that is not all there is to correctness of a program. If it were, we could just interpret the pre- and postconditions and leave out the programming language altogether. – Kilian Foth Jan 26 '18 at 9:22
  • No, it says that objects of type T can be replaced with objects of type S without altering any of desirable properties, such as correctness. More popular definition reads that "You can use a child object anywhere where its parent is used, without introducing errors". – Ezoela Vacca Jan 26 '18 at 9:24
  • @EzoelaVacca: You are using subtly different definitions of "correctness" here. Substituting a subtype, as defined by LSP, does mean that the program will (in a perfect world) remain technically correct, in a sense that it will behave in conformance to a specification of the (base) type. But in a more general, more informal sense, it will not necessarily do what its human users intended. – Filip Milovanović Jan 26 '18 at 9:52
  • @FilipMilovanović Check the other answer, which is correct. The condition lower than 100 remains correct even in the subtype, because lower than 50 is a "sub-set". – Ezoela Vacca Jan 26 '18 at 10:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.