Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types without altering program correctness.
Intent & Objective
If class S is a subtype of class T, then objects of type T may be replaced with objects of type S without breaking client expectations or violating contracts.
"Any brand of rechargeable AA battery can substitute for an alkaline battery in a remote control without breaking device invariants."
The Problem
The infamous Square-Rectangle problem: a Square inheriting from Rectangle overrides setters, meaning setting width also mutates height. Code expecting a rectangle computes an incorrect area or crashes when assumptions fail.
The Solution
Ensure derived classes adhere strictly to the preconditions, postconditions, and invariants of the base class. Favor composition over inheritance if a subclass cannot uphold all parent contracts.
Key Participants & Architectural Roles
Code Samples (Traditional OOP vs Functional Programming)
Available in C++, Python, and JavaParadigm Contrast: Traditional OOP vs Functional / Data-Oriented
In OOP, LSP enforces that subclasses adhere to behavioral subtyping and contracts. In Functional Programming, LSP is expressed via type systems, algebraic data types, and function signatures: a function expecting `(A) -> B` can safely accept any function with contravariant input and covariant return type.
Advantages
- β Guarantees predictable polymorphism across your entire codebase
- β Prevents subtle bugs where subclasses break tacit assumptions
- β Encourages safe class hierarchies
Trade-offs
- β Forces developers to rethink naive real-world taxonomies that do not translate into software subtyping
When to Apply
- β Whenever creating an inheritance hierarchy
- β When considering overriding base class methods that throw `NotImplementedException`
When to Avoid
- ! LSP is a fundamental design law; you should never deliberately violate it