Liskov Substitution Principle (LSP)
“Subtypes must be substitutable for their base types without altering the correctness of the program.” In practice, any class inheriting from a base class should be able to behave as the base class type without surprises or broken expectations.Purpose
- Guarantees reliable polymorphism – you can use a subclass wherever a base class is expected, without needing special case handling.
- Exposes bad inheritance designs: if a subclass cannot fully substitute for its parent type (i.e., it violates expected behavior or contracts), the design should be reconsidered.
- Ensures that derived classes extend (or specialize) base behavior without weakening or contradicting it (e.g., honoring all the base class’s invariants, postconditions, etc.).
Minimal Example
A classicLSP violation is modeling a Square as a subclass of Rectangle. A Rectangle might allow independent width and
height changes, but a Square (where width == height) can’t fully satisfy that contract. In the code below, notice that
substituting a Square into a function that expects a Rectangle breaks the expected behavior (the assertion fails).
This shows Square is not a true LSP-compliant subtype of Rectangle.
More Realistic Example
Consider a file writer API. A base classFileWriter provides a write() method. We might be tempted to create a
ReadOnlyFileWriter subclass that, say, throws an error on write. However, doing so means a ReadOnlyFileWriter cannot
actually substitute for a FileWriter – any code expecting a FileWriter would break if given the read-only variant.
This violates LSP.
💡 LSP in practice: The example above suggests that ReadOnlyFileWriter shouldn’t subclass FileWriter at all.
Perhaps ReadableFile and WritableFile interfaces should be separate. Following LSP often guides us toward better
abstractions or the Interface Segregation Principle, described next.