Inheritance helps us to follow the object-oriented principles, such as code reuse and polymorphism. However, there are some scenarios where inheritance may go wrong and that need to be carefully considered and managed using Design Principles.
In this article, we will explore a specific scenario and discuss how to effectively address the associated issues using design principles to achieve inheritance in the right way.
Liskov Substitution Principle (LSP)
The problem:
Generalization, which is also commonly known as inheritance, is one of the important concepts in Object-Oriented Programming.
The variables are the properties of an object, and the methods are its behaviours.
If there are some types of objects that behave in a similar or identical way, then we can generalize them into a single class, which becomes the superclass. Objects with additional or specialized behaviors can then be defined as subclasses, which inherit the properties and behaviors of the superclass while also having their own specialized behaviors.
In this way, we can reuse the code of the superclass whenever a new similar type of object is introduced. This approach allows us to write more maintainable and extensible code.
Let’s see an example of this approach.
Consider there is a Super class Animal.
We can utilize this class for inheritance by animals such as dogs, cats, and even chickens, which exhibit walking and jumping behaviours.
But what if we use this class for a ‘whale’?
Well, most of the behaviors such as general name, age, isMammal, eat, and play can be matched.
We may need to implement new specialized behaviours such as swim and rest.
Consequently, there will be some methods in the superclass that have no use in this subclass.
A whale cannot walk and sleep like other animals. However, these methods are available to the class Whale, which may lead to inconsistent behavior.
This scenario goes against the object-oriented principle called the ‘Liskov Substitution Principle (LSP)’.
Liskov Substitution Principle (LSP):
Another example:
Let’s see with another example. Consider there is a superclass Employee of a software company. An Accountant is an employee and Software engineer is also an employee.
An accountant who is maintaining the accounts details of all the employee need access the entire employee details of all the employee. This employee can be derived from Employee class where we define behaviors such as getemployeeDetails().
Software Engineer should not be able to access the personal details of other employees.
Here the Accountant can replace Employee but Software engineer cannot replace the Employee class.
What if we go against LSP?
📌When going against ‘Liskov Substitution Principle (LSP)’, the contract between a super class and sub class is violated and so the reusability is reduced.
📌It may cause unexpected runtime errors or wrong behaviors. A whale (also an animal) should not be able to walk. If we allow it then we are not defining a whale’s behaviors correctly. In the employee example, when deriving the Software engineer from the same Employee class may cause data breach & provides access to other employee’s personal information. This will cause serious issues in data privacy.
📌Stops or decreases the extensibility. Let’s take the Employee example. The employee class provides too much information than it is allowed and this stops other classes from inheriting.
Solution:
A solution to this problem is to correct the hierarchy of the inheritance.
We need to differentiate the types of objects and make the superclass more generalized.
In our Animal example, we can differentiate animals based on whether they live on land or in water, or based on how they move and sleep.
In the employee example, we can differentiate employees based on which employee can access which data or by the role of the employee.
Does this remind you of interface segregation? Yes! You can consider it as a solution to this problem.