Liskov Substitution Principle (LSP)

Definition:

This is a "design by contract" principle that states that any subclass must behave as we'd expect the superclass to behave. It is the L in Robert C. Martin's SOLID design principles; by his definition: "Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it." Liskov substitutability requires behavior equivalence from a subtype. This is not the same as interface substitutability.

Note also that Design By Contract does not imply (or require) use of the LSP. Design By Contract simply specifies the part of the behavior which must remain unchanged in a given scenario.

Triggers:

    • Errors could be raised by a subclass that are not expected/raised by the base class

    • Subclasses with more constraints than base classes

Forces:

The LSP should be used if:

    • There is no reason for the underlying processing to know about any more than the base class. (An example would be the Strategy pattern where the caller should not require any additional information about the specifics of the strategy.)

    • It is expected, or desired, that the code using on the base class will be reused elsewhere and may encounter alternate derived classes.

The LSP may not be completely appropriate if:

    • A subclass needs to strengthen preconditions. (The base class has a method that accepts any SomeBaseClass as a parameter, but the child class requires a ClassInheritedFromSomeBaseClass.)

    • A subclass needs to weaken postconditions. (The base class has a method that always returns a value in the range 1 - 100 but the child class needs to return a value of 101.)

    • A subclass needs to provide an exception condition that was not present in the base class (unless the exception object is itself a subclass of a valid exception object raised by the base class).

A common example showing a violation of the LSP is the classic "Is a circle an ellipse" question. At first glance, a circle could be considered a subclass of ellipse (because it implements the same interface), with the added requirement that height and width must match. This is a violation of the LSP because that constraint means that a circle does not behave the same way as an ellipse (an operation which attempted to set both height and width would experience a potentially undesirable difference in behavior). If you look at the behavior of a circle and an ellipse, it becomes clear why. The circle really only needs a setRadius operation while the ellipse would need both a setRadius and a setAspectRatio operation.

In the context of a specific application, enforcing LSP may be overkill. It doesn't matter if someMethod on ClassOne operates differently from someMethod on ClassTwo if that method is never invoked during a particular operation, but it may make sense for those methods to exist for use by other operations with different constraints elsewhere in the application.

Resources: