You are here

SOLID Principles : The Liskov Substitution Principle

SOLID Principles : The Liskov’s Substitution PrincipleAlmost 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.

What is Liskov Substitution Principle (LSP) ?

Simply put, if class A is a subtype of class B, then we should be able to replace objects of 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

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 Single Responsibility 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)

close

2 thoughts on “SOLID Principles : The Liskov Substitution Principle

  1. 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

Leave a Reply

Top