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.