Introduction
The Liskov Substitution Principle (LSP) is a foundational concept in object-oriented design, introduced by Barbara Liskov in 1987. It states:
"Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program."
In simpler terms, derived classes must be substitutable for their base classes without altering the expected behavior of the program. LSP ensures that a class hierarchy is designed in a way that promotes reusability and reliability.
Key Aspects of LSP
- Behavioral Consistency: Subclasses must adhere to the behavior defined by their base classes.
- No Surprises: A subclass should not override or weaken any functionality of the base class.
- Contracts: Subclasses should honor the "contract" (e.g., preconditions and postconditions) established by the base class.
Violating LSP often leads to fragile code that is hard to maintain or extend.
Bad code ❌
public class Rectangle
{
public virtual double Width { get; set; }
public virtual double Height { get; set; }
public double GetArea() => Width * Height;
}
public class Square : Rectangle
{
public override double Width
{
set { base.Width = base.Height = value; }
}
public override double Height
{
set { base.Width = base.Height = value; }
}
}
public class LSPViolationDemo
{
public static void Main()
{
Rectangle rectangle = new Square(); // Substitution occurs here
rectangle.Width = 4;
rectangle.Height = 5; // Expecting Width=4 and Height=5 for a rectangle
Console.WriteLine($"Area: {rectangle.GetArea()}"); // Output: 25, not 20!
}
}
What's Wrong? ❌
Substituting Square
for Rectangle
violates expectations. A rectangle can have different widths and heights, but a square enforces equal sides. The GetArea
result is incorrect in this context.
Adhering to LSP: A Better Design ✔
To adhere to LSP, avoid forcing subclasses into incompatible behaviors. In this case, separating Rectangle
and Square
into distinct hierarchies solves the issue:
public abstract class Shape
{
public abstract double GetArea();
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double GetArea() => Width * Height;
}
public class Square : Shape
{
public double SideLength { get; set; }
public override double GetArea() => SideLength * SideLength;
}
public class LSPAdherenceDemo
{
public static void Main()
{
Shape rectangle = new Rectangle { Width = 4, Height = 5 };
Shape square = new Square { SideLength = 4 };
Console.WriteLine($"Rectangle Area: {rectangle.GetArea()}");
Console.WriteLine($"Square Area: {square.GetArea()}");
}
}
Why This Works?
- Both
Rectangle
andSquare
derive from Shape, but they operate independently, adhering to their specific behaviors. - LSP is preserved because the substitution respects each class's expected behavior.
Benefits of Following LSP
- Improved Reusability: Subclasses work seamlessly with existing code.
- Ease of Testing: Code that adheres to LSP is predictable and easier to test.
- Enhanced Maintenance: Clear boundaries between classes make debugging and extending functionality straightforward.
Conclusion
The Liskov Substitution Principle is critical for creating robust and flexible object-oriented designs. By ensuring that subclasses can be used interchangeably with their base classes without causing unexpected behavior, you build systems that are easier to maintain and extend. When designing your class hierarchies, always ask: "Can this subclass replace its base class without altering the program's behavior?"
Following LSP not only strengthens your adherence to SOLID principles but also sets the foundation for scalable and maintainable software solutions. Happy coding!
Let connect on LinkedIn and checkout my GitHub repos:
Top comments (0)