The Liskov Substitution Principle (LSP) is a fundamental principle of object-oriented programming that was first introduced by Barbara Liskov in a 1987 paper. The principle is named after her and states that objects of a superclass should be able to be replaced with objects of a subclass without causing errors or unexpected behavior in the program.
In other words, if a piece of code is designed to work with a certain type of object, and a subtype of that object is substituted in, the code should still work correctly. This means that subclasses should not alter the preconditions, postconditions, or invariants of the methods of the superclass.
The LSP is closely related to the concept of polymorphism, which allows different objects to be treated as if they were of the same type. When code follows the LSP, it enables the use of polymorphism, which can lead to more flexible and reusable code.
To apply the LSP, the behavior of subclasses should be consistent with the behavior of their superclass. In other words, a subclass should not remove any functionality that the superclass has, nor should it add any new functionality that would break the contract established by the superclass. This ensures that the code that relies on the superclass can continue to work correctly with the subclass.
Overall, the LSP is an important principle for ensuring that object-oriented code is correct, robust, and maintainable.
Example
Here’s an example of how the Liskov Substitution Principle (LSP) can be applied in object-oriented programming:
Consider a simple class hierarchy that includes a Shape superclass and two subclasses, Rectangle and Square. Both Rectangle and Square have a getWidth and getHeight methods to retrieve their dimensions. The Shape class also has an area method to calculate the area of the shape:
public class Shape {
public int area() {
// Calculate area of the shape...
}
}
public class Rectangle extends Shape {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
}
public class Square extends Shape {
private int side;
public Square(int side) {
this.side = side;
}
public int getSide() {
return side;
}
public int getWidth() {
return side;
}
public int getHeight() {
return side;
}
}
According to the Liskov Substitution Principle, objects of the Rectangle and Square classes should be substitutable for objects of the Shape class. This means that if we have a method that takes a Shape as an argument, we should be able to pass in either a Rectangle or a Square and still have the method work correctly.
In this case, since both Rectangle and Square have a getWidth and getHeight method, they can be used interchangeably in code that expects a Shape object. For example, we could have a method that calculates the total area of a list of shapes:
public int totalArea(List<Shape> shapes) {
int total = 0;
for (Shape shape : shapes) {
total += shape.area();
}
return total;
}
This method can be called with a list of Rectangle and Square objects, and it will calculate the total area correctly. Because the behavior of the Rectangle and Square classes is consistent with the behavior of the Shape class, they can be used interchangeably without any unexpected behavior or errors.