Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without breaking the application.
LSP enforces something discussed so far: public inheritance should model an is-a relationship.
If class B
inherits from class A
: we can use pointers / references to B
objects in the place of pointers / references to A
objects: C++ gives us this.
Liskov Substitution is stronger: not only can we perform the substitution, but we should be able to do so at any place in the program, without affecting its correctness.
Precisely
- If an invariance is true for class
A
, then it should also be true for classB
- If an invariant is true for method
A::f
, andB
overridesf
, then this invariant must be true forB::f
- If
B::f
overridesA::f
- If
A::f
has a preconditionP
and a postconditionQ
, thenB::f
should have a preconditionP'
such thatP=>P'
and a postconditionQ'
such thatQ'=>Q
B::f
needs a weaker precondition and a stronger postcondition
Ex: Contravariance problem
- Happens whenever we have a binary operator where the other parameter is the same type as
*this
.
As we’ve seen before, we must take in the same parameter when overriding. C++ enforces this, which actually enforces LSP for us.
- A
Circle
is-aShape
- A
Shape
can be compared with otherShape
s - A
Circle
can be compared with any otherShape
To satisfy LSP, we must support comparison between different types of Shape
s.
typeidreturns
std::type_info` objects that can be compared.
dynamic_cast
: Is other
a Circle
, or a subclass of Circle
?
typeid
: Is other
exactly a Circle
?
typeid uses the dynamic-type so long as the class has at least one virtual method.
Example of Violation of LSP
Ex: Is a Square
a Rectangle
?
4th Grader: Yes, a Square is a rectangle.
A square has all the properties of a rectangle.
What is the issue here?
we expect postcondition for
Rectangle::setWith
to be that the width is set and nothing else changes. But this is violated by ourSquare
class.
Violates LSP, does not satisfy an is-a relationship. Conclusion: Square are not Rectangles.
Possibility: Restructure inheritance hierarchy to make sure LSP is respected.
In general: how can we prevent LSP violations?
We should restrain what our subclasses can do, only allows them to customize what is truly necessary.
We can use a design pattern: Template Method Pattern, to make this process easier.