SOLID Principles-The Liskov Substitution Principle Design Principles Core Java java by devs5003 - May 21, 2023July 29, 20244 Last Updated on July 29th, 2024Almost everywhere when we talk about delivery of a product, the first step comes in mind is it’s design. The more focus we put into the design, the better the product will look. Every design has some design principles that need to be followed while designing a product. Hence, design principles have a crucial role in any product delivery. Design Principles help teams with decision making. In this article, We will discuss about ‘SOLID Principles-The Liskov Substitution Principle’. A few simple principles or valuable questions can guide our team towards taking relevant decisions. SOLID Principles are the set of five principles used to design a software. In fact, the word ‘SOLID’ is the acronym for the set of five principles that contains the first letter of each principle. Let’s discuss ‘SOLID Principles-The Liskov Substitution Principle’ in detail and related concepts. Table of Contents Toggle What is SOLID Principles-The Liskov Substitution Principle?Why is it required to follow Liskov Substitution Principle?Example: Code that violates LSPExample: Code that follows LSPWhat is the benefit of Liskov Substitution Principle?FAQLinks to Other Design PrinciplesSingle Responsibility PrincipleOpen Closed Principle(OCP)Interface Segregation Principle(ISP)Dependency Inversion Principle(DIP) What is SOLID Principles-The Liskov Substitution Principle? Simply put, if class A is a subtype of class B, then we should be able to replace objects of B with objects of A (i.e., objects of type A may substitute objects of type B) without changing the behavior (correctness, functionality, etc.) of our program. “Derived types must be completely substitutable for their base types” In Layman’s terms, it states that an object of a superclass should be replaceable by objects of its subclasses without causing issues in the application. Therefore, a child class should never change the characteristics of its parent class (such as the argument list and return types). Basically, derived classes should never do less than their base class. LSP applies to inheritance hierarchies, specifying that you should design your classes so that client dependencies can be substituted with subclasses without the client knowing about the change. All subclasses must, therefore, operate in the same manner as their base classes. The specific functionality of the subclass may be different, but must conform to the expected behavior of the base class. To be a true behavioral subtype, the subclass must not only implement the base class’s methods and properties, but also stick to its implied behavior. In general, if a subtype of the super type does something that the client of the super type does not expect, then this is in violation of LSP. Imagine a derived class throwing an exception that the superclass does not throw, or if a derived class has some unexpected side effects. The definition of it might be a bit complex, but in fact, it is quite easy. The only thing is that every subclass or derived class should be substitutable for their parent or base class. Why is it required to follow Liskov Substitution Principle? This avoids overuse/misuse of inheritance. It helps us conform to the “is-a” relationship. We can also say that subclasses must fulfil a contract defined by the base class. A typical example that violates LSP is a Square class that derives from a Rectangle class. The Square class always assumes that the width is equal with the height. If a Square object is used in a context where a Rectangle is expected, unexpected behavior may occur because the dimensions of a Square cannot (or rather should not) be modified independently. I am not explaining this example as you will find it very easily from too many sources. Let’s have some other example. Example: Code that violates LSP Suppose a book store asks us to add a new type of book delivery functionality in the application. So, we create a BookDelivery class that informs customers about the number of locations where they can collect their order from as below: public class BookDelivery { String titles; Integer userID; void getDeliveryLocations() { ... } } However, the store also sells fancy poster maps they only want to deliver to their high street shops. So, we create a new PosterMapDelivery subclass that extends BookDelivery and overrides the getDeliveryLocations() method with its own functionality: public class PosterMapDelivery extends BookDelivery { @Override void getDeliveryLocations() { ... } } Later, the store asks us to create delivery functionalities for audiobooks, too. Now, we extend the existing BookDelivery class with an AudioBookDelivery subclass. But, when we want to override the getDeliveryLocations() method, we realize that audiobooks can’t be delivered to physical locations. public class AudioBookDelivery extends BookDelivery { @Override void getDeliveryLocations() { /* can't be implemented since * Audio book doesn't have * a physical location. */ } } We could change some characteristics of the getDeliveryLocations() method, however, that would violate the Liskov’s Substitution Principle. After the modification, we couldn’t replace the BookDelivery superclass with the AudioBookDelivery subclass without breaking the application. Example: Code that follows LSP In order to solve the problem, we need to fix the inheritance hierarchy. Let’s introduce an extra layer that better differentiates book delivery types. The new OfflineDelivery and OnlineDelivery classes split up the BookDelivery superclass. We will also move the getDeliveryLocations() method to OfflineDelivery. Next, we will create a new getSoftwareOptions() method for the OnlineDelivery class (as this is more suitable for online deliveries). For example, below code demonstrates the concept. public class BookDelivery { String title; Integer userID; } public class OfflineDelivery extends BookDelivery { void getDeliveryLocations() { ... } } public class OnlineDelivery extends BookDelivery { void getSoftwareOptions() { ... } } In the refactored code, PosterMapDelivery will be the child class of OfflineDelivery and it will override the getDeliveryLocations() method with its own functionality. AudioBookDelivery will be the child class of OnlineDelivery which is good news, as now it doesn’t have to deal with the getDeliveryLocations() method. Instead, it can override the getSoftwareOptions() method of its parent with its own implementation (for instance, by listing and embedding available audio players). public class PosterMapDelivery extends OfflineDelivery { @Override void getDeliveryLocations() { ... } } public class AudioBookDelivery extends OnlineDelivery { @Override void getSoftwareOptions() { ... } } After the refactoring, we could use any subclass in place of its superclass without breaking the application. Violations of LSP cause undefined behavior. Undefined behavior means that it works okay during development but blows up in production, or that you spend weeks debugging something that only occurs once per day, or that you have to go through hundreds of megabytes of logs to figure out what went wrong. What is the benefit of Liskov Substitution Principle? If your code follows the Liskov’s Substitutional Principle, you will have below benefits: 1) Code Reusability 2) Easier Maintenance 3) Reduced Coupling FAQ What is the main goal of LSP? The main goal of LSP is to ensure that derived classes or subclasses can be used interchangeably with their base classes, allowing for polymorphism and maintaining program correctness. How does LSP relate to inheritance in object-oriented programming? LSP is nearly related to inheritance, as it focuses on the behavior of derived classes when they inherit from a base class. It ensures that the derived class can extend or specialize the behavior of the base class without breaking any contracts. What are the common signs of a violation of LSP? Common signs of a violation include derived classes overriding methods in ways that are inconsistent with the base class, throwing unexpected exceptions, or requiring additional conditions not specified in the base class. What are the exceptions or cases where LSP might not apply? LSP applies mostly to class hierarchies built on inheritance. In some cases, composition or interfaces may be more appropriate than inheritance, and LSP may not be as directly relevant. How can I ensure that I’m following LSP in my code? To ensure LSP compliance, carefully review the contracts and behaviors of base classes and their derived classes. Ensure that derived classes adhere to the expectations set by the base class and don’t introduce unexpected behavior. How does LSP relate to the Open-Closed Principle (OCP) in SOLID? LSP and OCP are related SOLID principles. LSP ensures that derived classes can extend base classes without altering their behavior, enabling adherence to OCP, which states that software entities should be open for extension but closed for modification. Can you provide an example of LSP violation and its consequences in a real-world scenario? Suppose we have a base class Vehicle with a method startEngine(). A derived class ElectricVehicle violates LSP by not implementing this method, leading to runtime errors when trying to start the engine of an electric vehicle. What is the relationship between the Single Responsibility Principle (SRP) and LSP? SRP focuses on a class having a single reason to change, while LSP focuses on the behavior of derived classes. They are related as following LSP helps maintain SRP by ensuring that derived classes don’t introduce unrelated responsibilities. Does LSP apply only to classes, or can it also be extended to interfaces and abstract classes? LSP can be applied to classes, interfaces, and abstract classes. It’s about ensuring that derived types adhere to the contract specified by the base type, regardless of whether the base type is a class, interface, or abstract class. That’s all about ‘SOLID Principles-The Liskov Substitution Principle’. Moreover, Wikipedia defines this Principle like this. In order to learn the most commonly used design principles, kindly visit article on ‘OOPs Design Principles‘. Apart from ‘SOLID Principles-The Liskov Substitution Principle’, other principles are discussed as separate articles respectively. Links to Other Design Principles Single Responsibility Principle Open Closed Principle(OCP) Interface Segregation Principle(ISP) Dependency Inversion Principle(DIP) Related
Thanks for writing such awesome articles on the SOLID Design Principle. I loved reading all of them. The definition is explained to the point in a very simple way. Code examples are good as well. Hat’s off to the writer @devs5003 Reply
Really good explanations this helped me grasp the content in a simple form that i can use when tackling more challenging questions. Great Work! Reply