Understanding the Liskov Substitution Principle: A Deep Dive into SOLID Principles

Ahmed Taha ElElemy
3 min readAug 10, 2023

--

Note: This is the third in a series of articles that explore each of the five SOLID principles of object-oriented programming.

In the world of software engineering, SOLID principles play a pivotal role in building maintainable and scalable systems. Among these principles, the Liskov Substitution Principle (LSP) is perhaps one of the most misunderstood. Named after Barbara Liskov, who introduced it in a 1987 conference keynote, LSP is integral to ensuring that a system remains easy to manage and control.

What is the Liskov Substitution Principle?

The Liskov Substitution Principle states: “If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program (correctness, task performed, etc.).”

In simpler terms, any implementation of an abstraction (interface) should be substitutable anywhere that abstraction is accepted.

Why is LSP Important?

LSP is crucial to the maintainability of your code. When LSP is violated, it often leads to strange, hard-to-find bugs. If your code relies on a specific subclass behavior, but you’re using a base class reference, things can go awry. Adhering to LSP helps avoid these bugs and makes your code more robust and flexible.

Liskov Substitution Principle in Action

Let’s delve into a practical example to understand LSP better. We’ll use a popular example involving shapes.

class Rectangle {
protected int width, height;
    public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setWidth(height);
super.setHeight(height);
}
}

In this example, a Square is a subtype of Rectangle and overrides setWidth and setHeight methods. On the surface, this seems okay because a square is a rectangle. But let's see what happens when we write a function that uses these classes.

void printArea(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
System.out.println("Expected area of 20, got " + r.getArea());
}
void main() {
Rectangle r = new Rectangle();
printArea(r); // Expected area of 20, got 20
Square s = new Square();
printArea(s); // Expected area of 20, got 16
}

This example violates LSP as the Square cannot be substituted for a Rectangle without altering the behavior of the printArea function. When we replaced the Rectangle with a Square in the printArea method, the result was different from what we expected.

Case Study: Refactoring Code to Respect LSP

So, how can we refactor the above code to adhere to LSP?

One potential solution might be to eliminate the Square subclass altogether and create a separate interface for shapes with areas. This solution would respect the LSP as there would be no inheritance relationship between different shape types. Here's what that might look like:

interface ShapeWithArea {
int getArea();
}
class Rectangle implements ShapeWithArea {
protected int width, height;
// ... existing Rectangle methods ... @Override
public int getArea() {
return width * height;
}
}
class Square implements ShapeWithArea {
private int side;
public Square(int side) {
this.side = side;
}
@Override
public int getArea() {
return side * side;
}
}

In this refactored code, a Rectangle and a Square are both ShapeWithArea types, and they can be used interchangeably without changing the program's behavior.

Conclusion

The Liskov Substitution Principle is a powerful concept that enables developers to write more maintainable and scalable code. While it may seem complex initially, understanding and applying it in your projects will greatly increase the quality of your code. Remember, SOLID principles are not rules, but guidelines to help you design better systems. Keep exploring, and stay tuned for the next article in this series on the Interface Segregation Principle.

--

--

Ahmed Taha ElElemy

Passionate Flutter dev & tech enthusiast. Collaborative problem-solver, eager to create innovative solutions. Let's code and build together!