Among all the SOLID principles for object-oriented programming paradigm, the Liskov Substitution Principle is one of my favorites. This principle was created by Barbara Liskov and has the main objective of avoid throwing exceptions in a system when inheritance is not used in a recommended way. Additionally, this principle has the intention of facilitating the overall maintenance of the code over time, prevent the creation of complex hierarchies for classes.
Inheritance in Object-Oriented Paradigm is the feature that allows us to reuse the implementation from a parent class across the system within child classes, which represents one of the main advantages of this paradigm. But, when we are designing classes for a particular domain that we are trying to solve or abstract, some good practices (or bad ones) can affect the overall maintainability of the software in long term.
Basically, inheritance is intended to be used between classes that have enough similarities to justify the use of this resource. If the child classes start having properties and methods that don’t make sense anymore for them, even they come from a parent class, it is time to re-think the inheritance to be sure that should still exist.
To demonstrate properly the Liskov principle, let me propose a scenario. Imagine a hypothetical scenario where we are working on a Streaming Service platform system that has, initially, two types of account types for users: premium and standard. The standard account has access to a limited number of movies/series, and the premium account has unlimited access to all movies/series and can share access with other family members.
Obviously, the standard and premium accounts have things in common and it is natural to create a base class that would be the parent classes for each of the account types, as shown in the following image:
In the implementation of the specialized classes for standard and premium classes, we want to make sure the Standard user does not have the same access as the Premium user. In many cases that I’ve seen in real scenarios (legacy code, etc.), we could have the following implementation for the child classes:
As you can see in the previous two images, both classes (standard and premium subscribers) are inheriting from the Base Subscriber class. The objective of not allowing Standard users to give access to family members is being met. But, obviously, that is not a good approach and you may think:
“what a stupid example! It is obvious that we should have a base class only with the properties and leave the specialized methods to the child classes!”
This kind of mistake is more common in scenarios where the system has already a bunch of legacy classes, with a bunch of methods and properties on each of them. The business requirements changed overtime, but the parent and child classes don’t reflect anymore the domain or problem that the code is meant to solve or abstract.
Basically, if a child class (premium class for instance) cannot replace the parent class perfectly and vice-versa, it is a strong indication that something needs to reviewed in the model. There are two approaches in that case to apply the Liskov principle:
- Specify in the parent class only the common properties and methods, leaving any specialization to the child classes
- Segregate the parent class in multiple interfaces that will be implemented by the underlying proper child classes
Following the option 1, we would have the following class structure:
The classes for the Standard and Premium Subscribers are different enough from the BaseSubscriber class to remove the unnecessary methods from the parent class. In real-world scenarios, with classes that may contain almost a hundred methods, this differentiation is important to avoid confusion and make everything worst overtime.
If you have only one method or property that does not make sense for one or more classes that are inheriting from it, maybe it is time to re-think the model structure. The same reflection point often occurs on database model when we start having nullable columns referring foreign keys. There is no problem with that, but if that is happening frequently, maybe there is something wrong with the model.
The second option is to create specialized interfaces for each case, as seen in the following image:
There is no rule regarding how similar a parent and a child class should be in order to remove that relationship,therefore I would not state here a specific number. But, if there is a single property or method in the parent class that is not applicable to a child class, it is already time to review it, in my humble perspective.
Please, feel free to give your opinion on this topic. Can you tell a specific case in your experience that would make the code better if the Liskov principle was used?
Extra info on:
C# Best Practices - Dangers of Violating SOLID Principles in C#
May 2014 Volume 29 Number 5 Brannon King | May 2014 As the process of writing software has evolved from the theoretical…
Thank you for reading this article. Feel free to reach me out and follow me on social media profiles:
I’m glad to announce that I have my first book published. It is a deep dive hands-on through the most common Design Patterns used in .NET applications. The book contains hundreds of code samples and explanations based on real-world scenarios. It also has many examples of Object-Oriented Programming, SOLID principles, and all the path to get yourself familiar with .NET 5 and C#. Check it out: