SOLID Series — 3/5 — Liskov Substitution Principle

Ioannis Papikas
3 min readApr 27, 2017

--

This article is part of the SOLID series which describes in depth all the S.O.L.I.D. principles, including examples, good practices, what are the common violations and why it benefits the developer and the product itself.

The Liskov Substitution Principle can be the most complicated principle of the SOLID acronym, because it deals with Semantics and the sub-type requirements.

Definition

If S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e. an object of type T may be substituted with any object of a subtype S) without altering any of the desirable properties of T (correctness, task performed, etc.).

We can see in more detail that the Liskov Substitution Principle is about bad usage of inheritance in cases where developers mix the real life inheritance with coding inheritance.

According to the definition and other articles, we can also add the following sentence:

Subtype Requirement: Let f(x) be a property provable about objects x of type T. Then f(y) should be true for objects y of type S where S is a subtype of T.

The above sentence describes the need to allow all sub-classes of a class to have the same expected and assumed behavior as the class that they inherit.

Common pitfalls and bad practices

A common example to describe the bad design according to this Principle is the square-rectangle example and other writers are using different examples like birds and penguins, all absolutely correct.

The basic mistake that most developers do is to confuse the real life inheritance (“is a”) with coding inheritance, which end up in bad coding practices, a lot of special cases in your code (if statements like “if object is A do this, else do that”). Not to be mistaken, the code will run as it is expected to but the design and the maintenance of the code will be problematic.

Square-Rectangle Case

This case describes the fact that a square, in real life “is a” rectangle but which has (four) equal sides. However, in coding we cannot really extend this behavior to a square because it changes the assumed behavior of the rectangle object in the first place.

In the following example we can see that a Square extends the Rectangle class and changes the width and height on every setter, which is wrong.

We can see the above problem appearing when we try to create some unit tests:

We can see that in the above example, the function testDimensions accepts a Rectangle object and tests against its basic assumptions. From the above test cases, only the testRectangle function will pass all tests and testSquare will fail.

We can see here a good example of the Liskov Substitution Principle and its violation. In order to fix the above problem, a good solution is to create an abstraction of Rectangle and Square as a common behavior and re-design these classes to extend the common behavior.

Conclusions

The above substitution example is focused on the common behavior of the second class inheriting the first one, and as a general rule to allow your program to run properly without special cases in your code.

The substitution action is accepted when it’s within the scope of the common behavior, meaning that it doesn’t forbid classes to add extra functionality to an object rather than changing the existing assumed behavior. You shouldn’t expect your program to work if you change a ClassB (extends ClassA) with ClassA because ClassB might have extra functionality that can cause your program to crash.

If you liked the article let me know by pressing the Recommendation button or even leave a comment to discuss. Feel free to share and discuss your approaches :-)

--

--