SOLID: Liskov Substitution Principle
All programs are composed of functions and data structures. The SOLID principles introduced by Robert C. Martin, help us group our code into modules and advise how to interconnect them. The whole point of application architecture is to manage change. Software requirements change all the time and we as developers need to make sure that these changes don’t break existing code.
There are 5 principles that make up the acronym are:
- S: Single Responsibility
- O: Open — Closed
- L: Liskov Substitution
- I: Interface Segregation
- D: Dependency Inversion
In this post, I will attempt to explain the LSP (Liskov Substitution Principle).
We use inheritance to share the superclasses code base.
Most of the time when building systems using OOP, we’re looking to use composition over inheritance, however, when the need is there to use inheritance it’s advisable that it’s done using the LSP. The Goal of this principle is to prevent our old codebase from breaking due when doing inheritance.
LSP may seem like common sense as you might already be doing this.
The mathematical definition of the Liskov Substitution Principle states:
If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program
If you’re seeing that for the first time I’m sure you’re having a hard time understanding it.
Simply put, your program should allow you to replace objects with objects of its subclass
Let’s illustrate:
Here we have a superclass Vehicle with a subclass Bus. When we run the program using a vehicle object we get:
Engine started...
Moving...
Now let’s change the main program and substitute the superclass object(new Vehicle) with the subclass one(new Bus) and run again
Engine started..
BUS Moving...
Great, the Bus subclass inheritance of Vehicle passes LSP! You are able to swap the superclass object out for subclass one.
Now let’s create a Helicopter subclass:
Because Helicopters don’t drive we are forced to throw a meaningful exception on the “drive” method. This will of course break the program:
HELICOPTER engine started...
throw new Error("HELICOPTER Does not drive")
^
Error: HELICOPTER Does not drive
This subclass, unfortunately, violates LSP. Yes, a Helicopter might be a vehicle that can transport people or cargo but in the context of our program Helicopter has no place inheriting from the Vehicle superclass. It fails LSP and needs to be removed from the inheritance tree.
Good indications that a subclass is violating LSP are
- We have to change the main program to accommodate the subclass
- We have to override irrelevant methods inappropriately (e.g. throwing exceptions)
- We have to restrict overridden methods to behave in a way that is not in line with the concept of the superclass
In Conclusion
Because class inheritance forces us to predict how our program might change, it’s best to avoid it, however, if we are required to use class inheritance (maybe for functionality or attribute reuse) then try to adhere to the Liskov Substitution Principle.