In this post, I am going to discuss the third design principle of SOLID, which is the Liskov Substitution Principle.

The SOLID principles are a set of five principles for object-oriented class design. These best practices and rules should be followed when designing a class.
The SOLID acronym stands for –
S – Single Responsibility Principle
L – Liskov Substitution Principle
I – Interface Segregation Principle
D – Dependency Inversion Principle
Liskov Substitution Principle (LSP)
Liskov substitution principle states that the objects of a superclass should be replaceable with objects of its subclasses without breaking the application.
In other words, subtypes must be substitutable for their base types.
Example 1
To understand this let’s take an example –
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class A { public void doSomething() {} } class B extends A { @Override public void doSomething() {} } A obj = new A(); obj.doSomething(); //Replaced with the object of child class A obj1 = new B(); obj1.doSomething(); |
In the above code example, class B is a child class of A. As per Liksov substitution principle, we should be able to replace objects of A with Objects of B without changing it’s behavior (correctness, functionality etc.) of our program.
A child class should never change the characteristics of it’s parent class.
Example 2
Let’s take an example of a Rectangle class. In a Rectangle, we know that two parallel sides are equal to each other. So, it has two properties: width and height.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class Rectangle { private int width; private int height; public Rectangle() {} public Rectangle(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } //Calculate the area public int calculateArea() { return this.width * this.height; } } |
Let’s me create another class Square which is extending Rectangle class. So, there exists IS-A relationship. Square IS-A rectangle. We all know square has all it’s side equal.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Square extends Rectangle { public Square() {} public Square(int side) { super(side, side); } @Override public void setWidth(int width) { setSide(width); } @Override public void setHeight(int height) { setSide(height); } public void setSide(int side) { super.setWidth(side); super.setHeight(side); } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class RecDemo { public static void main(String[] args) { Rectangle rectangle = new Rectangle(); verify(rectangle); Rectangle square = new Square(); verify(square); } public static void verify(Rectangle r) { r.setWidth(20); r.setHeight(30); System.out.println(" Expected " + 600 + " Output " + r.calculateArea()); } } |
|
1 2 3 4 5 |
Output - Expected 600 Output 600 Expected 600 Output 900 |
Our Square class extends Rectangle, but it modifies the expected behavior of Rectangle, which violates LSP.
How to Fix the LSP Voilation
To fix this problem we can use composition instead of inheritance.
Inheritance Vs Composition in Java
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
abstract class Shape { public abstract int calculateArea(); } class Rectangle extends Shape { protected int width, height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } @Override public int calculateArea() { return width * height; } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class Square extends Shape { private int side; public void setSide(int side) { this.side = side; } @Override public int calculateArea() { return side * side; } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class RecDemo { public static void main(String[] args) { Rectangle rectangle = new Rectangle(); verifyRectangle(rectangle); Square square = new Square(); verifySquare(square); } public static void verifyRectangle(Rectangle r) { r.setWidth(20); r.setHeight(30); System.out.println("Expected 600, Output: " + r.calculateArea()); } public static void verifySquare(Square s) { s.setSide(20); System.out.println("Expected 400, Output: " + s.calculateArea()); } } |
Important Points –
1. LSP applicable where there is supertype-subtype relationship. Either by extending a class or implementing an interface.
2. We need to make sure that new derived classes are extending the base classes without changing their original behavior.
3. If a subtype of the supertype does something that the client of the supertype does not expect, then this is the violation of LSP.