The Liskov Substitution Principle (LSP) is one of the five SOLID principles of object-oriented programming, formulated by Barbara Liskov. It states:
"Objects of a superclass should be replaceable with objects of its subclass without affecting the correctness of the program."
In simpler terms, if class B is a subclass of class A, then objects of class A should be replaceable with objects of class B without breaking the application.
Why is LSP Important?
LSP ensures that a derived class extends the behavior of a base class without altering its fundamental characteristics. Violating LSP can lead to unexpected behaviors, breaking polymorphism and making code more complex to maintain.
Example of LSP Violation
Incorrect Example (Violating LSP)
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;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // Enforcing square behavior
}
@Override
public void setHeight(int height) {
this.width = height;
this.height = height; // Enforcing square behavior
}
}
public class LSPViolationExample {
public static void main(String[] args) {
Rectangle rect = new Square(); // Substituting subclass
rect.setWidth(4);
rect.setHeight(5);
System.out.println("Expected Area: " + (4 * 5)); // Expecting 20
System.out.println("Actual Area: " + rect.getArea()); // Output: 25 (Incorrect!)
}
}
Why is LSP Violated Here?
- The
Square
class breaks the behavior ofRectangle
by forcing the width and height to be the same. - The program expects the area to be
width * height = 4 * 5 = 20
, but sinceSquare
modifies both dimensions, the actual area is5 * 5 = 25
, causing unexpected behavior.
Correct Example (Following LSP)
To fix this, we should avoid modifying inherited behaviors in a way that breaks expectations. A better approach is to use separate abstractions for Square and Rectangle.
abstract class Shape {
public abstract int getArea();
}
class Rectangle extends Shape {
protected int width;
protected int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public int getArea() {
return width * height;
}
}
class Square extends Shape {
private int side;
public Square(int side) {
this.side = side;
}
@Override
public int getArea() {
return side * side;
}
}
public class LSPExample {
public static void main(String[] args) {
Shape rect = new Rectangle(4, 5);
Shape square = new Square(4);
System.out.println("Rectangle Area: " + rect.getArea()); // 20
System.out.println("Square Area: " + square.getArea()); // 16
}
}
Why is This Correct?
- The
Shape
abstract class defines a common contract (getArea()
), butRectangle
andSquare
implement their own behaviors separately. Rectangle
andSquare
do not override each other’s behavior, ensuring LSP compliance.- Objects of
Rectangle
andSquare
can be used interchangeably without breaking expected behavior.
Key Takeaways
- Follow LSP by ensuring that subclasses do not break the expectations set by their base classes.
- Avoid overriding methods in a way that alters the base class's behavior incorrectly.
- Use separate abstractions when different behaviors are needed, instead of forcing a subclass to fit.
- Design classes such that a subclass can be substituted for its parent without causing unexpected behavior.
0 comments:
Post a Comment