LSP - Liskov Substitution Principle

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

The Liskov Substitution Principle (LSP) applies to inheritance in such a way that the derived classes must be completely substitutable for their base classes. In other words, if class A is a subtype of class B, then we should be able to replace B with A without interrupting the behavior of the program.

Several circumstances may indicate that the LSP has been violated:

  • A subclass changes the behavior of the parent class.

  • A subclass overrides the methods of the parent class but throws an exception.

  • A subclass overrides the methods of the parent class, but with an empty implementation.

  • A subclass inherits methods from the parent class but is not used.

There are two ways to solve this problem:

  • Breaking the hierarchy.

  • Tell, don’t ask.

Example 1 - a subclass changes the behavior of the parent class

To illustrate this, we will go with a classic example of squares and rectangles that people often use to explain LSP because it is very simple and easy to understand.

Looking at the example above, we see that all operations are very reasonable. Since a square has 2 sides, every time we set the length of one side, we reset the length of remaining side.

However, the Square class inherits from the Rectangle class but the Square class has different shapes and it changed the behavior of the Rectangle class, resulting in the LSP violation.

As the example above, because class Square inherits from class Rectangle, we can use the following:

The result is obviously wrong, the area of the rectangle should be 5 * 10 = 50.

According to this principle, we must ensure that when a class inherits from another class, it will not change the behavior of that class.

In this case, to avoid the LSP violation, we have to create a superclass, e.g. Shape class, and then for Square and Rectangle to inherit this Shape class.

Solution: Break the hierarchy

Example 2 - a subclass changes the behavior of the parent class

As you can see, with this implementation we have to check the instance type and call another method before calling getDiscount(). It violates LSP.

Solution: "Tell, don't ask"

Example 3 - subclass throws an exception

Another case of LSP scope is a subclass that throws an exception.

The above implementation classes have no problem, everything runs fine. Now we add a new SystemFileService class. With the requirement not allowed to delete the file system, this class will generate an UnsupportedOperationException error. A method that is designed but not used is not a good design either.

When executing the deleteFiles() method, the SystemFileService class throws an error at runtime. It cannot replace its parent FileService class, so it is already LSP violation.

Solution: Create a new interface

Example 4 - subclass with an empty implementation

The above implementation looks good. However, we now have a MasterDataRepository, with requirements that the master data is read-only, cannot be added, modified, or deleted. We have the implementation as follows:

Because class MasterDataRepository inherits from class CrudRepository, we can use the following:

Solution: Create a new interface

Last updated