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 Rectangle
.
However, we can see the semantic difference, when we test #area()
.
Because a rectangle can have a different height than its width the result of #area()
adheres to our semantic definition.
If we replaced Rectangle
with 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 Rectangle
and 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 width
and height
private.
In Conclusion
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.
Top comments (0)