Principles, Patterns, and Practices Chapter 10: The Liskov Substitution Principle (LSP)
LSP exists to correct a subtile misunderstanding in OOP. I was taught to use inheritance whenever a ‘is-a’ relationship exists. However, a subtype should be defined as ‘substitutable for’ in a given context. This context can either be inferred or can be explicitly expressed with pre- and post- conditions.
Here is an example of a LSP violation: A square is a rectangle and so inherits the class rectangle. A square redefines setWidth() to make width and height the same. A class that uses Rectangles can reasonable assume that setWidth() and should not mutate the height. This leads to errors that are hard to diagnose because they throw far away from their subtile logic causes.
With LSP preconditions in children should not be more restrictive as their parents, and post-conditions should not implement less functionality than their parents.
LSP is most often archived with abstract parent classes that define shared responsibility. A software engineer should also recognize when context that derivatives do not include exceptions that their parents do not have.
The context of calling code can be hard to assume. As such LSP should be balanced against unneeded complexity. LSP can be violated and documented when the costs outweigh the benefits.