The Liskov Substitution Principle (LSP) (introduced by Barbara Liskov in 1987) states that "objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program."
In simpler terms... if we use a class somewhere in our code, and we extend that class (inheritance), we must be able to replace it with any of the child classes, and the program should still be valid.
# Substitution and Correctness?
- Substitution: Replacing instances of a base class with instances of subclasses.
- Correctness: The program continues to work as expected after the substitution.
The Liskov Substitution Principle ensures that base classes are fully substitutable by their derived classes, meaning that subclasses must not change the expected behavior of the base.
# Why is it important?
Reliability
Ensures that components are interchangeable without undesirable side effects.
Reusability
Facilitates code reuse by ensuring that subclasses act as expected.
Maintainability
Code is easier to maintain as relationships between classes are clear and predictable.
# Symptoms of violation
We can identify when we are not respecting the Liskov Substitution Principle:
- Need to check the type of a subclass before using a method.
- Subclasses throwing exceptions in methods that the base class handles.
- Clients of the base class cannot use subclasses without knowing the difference.
Example
Let's imagine an application that manages geometric shapes.
Without Liskov Substitution Principle
public class Rectangle { protected int width; protected int height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public int getArea() { return width * height; } } public class Square extends Rectangle { public void setWidth(int width) { this.width = this.height = width; } public void setHeight(int height) { this.width = this.height = height; } }
If we use a Square instead
of a Rectangle, it
can cause unexpected behavior; changing the height of a Square also
changes its width.
With Liskov Substitution Principle
We design the classes to ensure substitutability through a common abstraction:
public abstract class Shape { public abstract int getArea(); } public class Rectangle extends Shape { private int width; private int height; // setters and getters public int getArea() { return width * height; } } public class Square extends Shape { private int side; public void setSide(int side) { this.side = side; } public int getArea() { return side * side; } }
Conclusions
By implementing the Liskov Substitution Principle, we
ensure that Square and
Rectangle can be used
interchangeably where a Shape
is expected. This eliminates the possibility of unexpected side effects due to incorrect
assumptions about the behavior of subclasses.
Summary
The Liskov Substitution Principle is essential for a robust and maintainable software design. It encourages a consistent and predictable class hierarchy, which in turn facilitates extensibility and code reusability.