The Liskov substitution principle, is the L in SOLID principles. It was created by Barbara Liskov and states:
Subtypes must be able to substitute base types.
Aside from the obvious implementation of this principle -- ensuring the subtype has the same behavior as the base type -- the implicit implementation is ensuring the subtype has the same semantic behavior as the base type.
Semantic is defined as:
Relating to meaning in language or logic.
And so, we create a definition of what the class name means. Typically, squares and rectangles are defined through their mathematic definition. Mathematically, a square is a shape with four sides in equal lengths. For rectangles, we will be using the definition of a perfect rectangle, meaning a shape where the top and bottom sides have equal lengths and left and right sides have equal lengths.
From this definition, we can conclude that a square's width and height must be the same while a rectangle can have a width different from its height as well as having a width equal to its height.
Let's create classes for these shapes, where we just define the width and height.
We can see that a
Square object can easily replace a
Rectangle object in a given situation. All
Square objects share the same behavior as
However, we can see the semantic difference, when we test
Because a rectangle can have a different height than its width the result of
#area() adheres to our semantic definition.
If we replaced
Square the test will pass, but the
Square object is no longer behaving the way we expect.
Because a square's width and height cannot be different, the
Square object breaks our semantic definition. This breaks the Liskov substitute principle.
The easiest way to ensure both
Square pass the test, while satisfying LSP, is to remove line 4.
You may be thinking, why not implement a
#setWidth() function in
Rectangle that can be overwritten in
Square? This would cause
Square to only share the interface of
Rectangle instead of its behavior, making the abstraction unnecessary. By removing line 4, we can satisfy LSP and we can go even further by making
When implementing inheritance, be sure to adhere to the meaning of the class outside of the implementation. This helps the class become more understandable in the grand scheme of the project.
Another technique that helps ensure classes adhere to LSP is design by contract.