Liskov substitution principle
This is part three of a five part series covering the SOLID principles. For more information on what the SOLID principles are, check out part one.
- Single responsibility principle
- Open/closed principle
- Liskov substitution principle
- Interface segregation principle
- Dependency inversion principle
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
In object oriented programming, code is organised in objects. An object is represented as a class and will contain related code that makes sense to be grouped together. A class will define one thing and it's name will be a noun.
At some point you will need to create different types of objects that share similar code. At this point you'll run into inheritance. For example, you could have a base class called Vehicle which would contain core vehicle functionality. Then you could create child classes called Car, Bus and RaceCar. The child classes will inherit code from Vehicle, override and add new methods to make it's own object.
What does the rule mean?
The rule states that you should be able to use the child class anywhere you would use another sibling class. Say you have a class called A and two child classes called B and C. Anywhere in the code base where you are using the B class, you should be able to swap it with the C class, without modifying the exisiting code implementation.
If you find yourself adding in special rules to cater for a child class then it's a sign that it might not belong as a child class.
You are working on project that has a TouchDevice class. On the class there are few methods, one being swipe. Anytime a user swipes a touch device this method will be invoked.
A new project requirement has come in which will require you to create two new child classes. You create two new classes, Iphone and Ipad. The requirement is solved and no volilations have occured. Both the Iphone and Ipad will inherit and use the swipe method from the TouchDevice class.
Another project requirement comes in and this time it's trickier. To solve it you'll need to create a Macbook class. You decide the best way to solve it is to create the Macbook class as a child of TouchDevice. Reasoning that they share nearly all the same methods, expect for swipe. In the Macbook class you overide the swipe method and throw an error if that method is called.
The requirement is solved but you've voliated the rule. The Macbook class is no longer subtitutable with another class. A developer may interact with the new Macbook class, thinking it behaves as a TouchDevice. The developer calls the swipe method assuming that it will work and the software will error unexpectedly.
Instead we should have looked at creating the Macbook class in a different way, where it was not a child of TouchDevice. Perhaps it could of been it's own class and not a child of TouchDevice. or perhaps we could of refactored TouchDevice to be Device and injected a Navigation class, which contained the swipe method to the classes that needed it.
Why it is important?
- Predictable. Having confidence in how the code will work, will lead to less bugs and faster development.
- Decoupled. Since you have an expectation that classes with a common parent will work similarly, you don't need to do type checking to see if x supports y.
How can I use it?
Identify code that violates the rule and come up with a way to fix it. It could be a different abstraction is needed or it might not belong as child class.
That's the Liskov substitution principle, if you have any questions about it ask a question in the comments section.
If you want to see how another person explains it then check out the links below. They are both short videos which go over the same principle.