I am trying to wrap my mind around a Liskov Substitution Principle and I came across this article.

SOLID Class Design: The Liskov Substitution Principle

In this article, the author gives an example of a Flightless Bird(Penguin) to extend a general Bird class that would be closed for "addition to the new type of birds."

Is it not violating the Open/Closed Principle ? Or is defining a sub-class allowed in maintaining for Open/Closed with Liskov-Substitution Principle?

  • 3
    see Discuss this ${blog} – gnat Sep 26 '18 at 6:46
  • 3
    Can you please explain your question in the question itself rather than linking to an external article? Without reading that blog post, it is not clear what you are asking about. – amon Sep 26 '18 at 9:18

This is a very interesting article.

Open/Close principle

Defining a subclass does not break the open/close principle, since it means "open for extension / closed for modification". So:

  • One can perfectly add Penguin subclass to extend Bird
  • But changing the interface of the Bird class to add support for birds that can't fly is definitively a modification.

The Bird class was flawed from the beginning

What does the example shows us ? A very practical case of how to deal with a flawed design. And if the design is not viable, you have to change it.

The Bird class was flawed since the beginning, because it didn't take into account the fact that there are birds that can't fly, be it the whole class Penguin or a single instance of injuredBird.

The class is even more flawed than it seems. Because all birds have ranges of altitude: kingfishers can have a slightly negative altitude under water. And no birds can't fly in the stratosphere: Some species can fly up to 11.300 km but some others are limited to 8.000. And Penguins are limited between 0 and 0 above the ground (in fact they can dive, so negative altitude is allowed also.

Liskov Substitution Principle in all this ?

Looking at the example code, adding Penguin to the original Bird is in fact not a problem according to the LSP, because no promise is made on the result of setAltitude().

The LSP tells us that:

  • preconditions cannot be strengthened.
  • postconditions cannot be weakened. There is no claim made that the altitude will be set to the value provided, so there's no weakening. If the postconditon is that the new value of altitude is between 0 and the value provided, the extension would match the rules by leaving the altitude unchanged. Btw.us, this post-condition would also allow for injuredBird and newBornBird.
  • invariants must be preserved.
  • history constraint shall be ensured. This implies that the state and behavior of a Bird only depend on the use of its defined interface. So for example, Penguin could have no other method that would change the altitude without relying on setAltitude().
  • 1
    Have a +1 from me! To make your answer even better, you should also mention the History Rule. The three rules you mention are enough for functional programming with immutable objects (they are essentially a re-phrasing of the variance rules for function subtyping, i.e. functions are contravariant in their inputs and covariant in their outputs, in the language of contracts). This contribution of Liskov was definitely important, but the much more important contribution of hers was the History Rule, which makes subtyping work with mutable objects and aliasing. – Jörg W Mittag Sep 26 '18 at 7:52
  • All of the approaches to subtyping before Liskov did not work in the presence of mutability and aliasing. – Jörg W Mittag Sep 26 '18 at 7:53
  • @JörgWMittag thanks for the suggestion. I'm a fan of history constraint. I did not want to overload OP with too much at once. But you're right: it's fundamental, so I edited accordingly. – Christophe Sep 26 '18 at 11:05
  • 1
    +1. Your argument becomes IMHO clearer if one imagines additional properties in the Bird interface like maxAltitude and minAltitude and an invariant which guarantees that the bird's altitude stays always between the min and max value (for example, by letting setAltitude cutting too large values down to maxAltitude). Then a penguin is in no way special compared to other birds. – Doc Brown Sep 26 '18 at 12:27
  • 1
    @Henrique If the subclass has additional public methods but that it completely fulfils the contract of the parent, it does not violate LSP: you could always use the subclass instead of the parent. – Christophe Nov 26 '20 at 21:21

Is it not violating the Open/Closed Principle or defining a sub-class is allowed in maintaining for Open/Closed and Liskov-Substitution Principle?

The Open/Closed principle must be understood in a context where the design is already correct, i.e. devoid of other OO flaws. If you modelled a problem the correct way, OCP tells you that you can then extend a class by subclassing it but shouldn't modify it.

What the article tells us is that the problem wasn't modelled correctly in the first place. What's really at stake here is that behind most LSP violations lurks an implicit property - here, it is the post-condition :

The bird's altitude must change after you've called setAltitude()

Adding an extra FlightfulBird class solves this by making the property apparent (at least conceptually - we could have made it concretely correct with an explicit post-condition). But it has nothing to do with compliance to the Open/Closed principle, it is only a way of getting the design right, before OCP even comes into play.

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.