Liskov Substitution Principle
No discussion of inheritance would be complete without mentioning the
Liskov Substitution Principle
(LSP), the “L” in the SOLID acronym. Barbara Liskov
introduced a “substitution property” in 1988. There’s a more formal
definition, but essentially it means that if you have some code that
works with some type T
and you instead give it something of type
S
, then if the code continues to work correctly S
is
substitutable for T
.
The LSP suggests that subclasses should be substitutable for their parent classes. That is, an instance of a subclass should be able to be used by code that expects to work with an instance of its superclass without fear of negative consequences.
There is some debate about whether this is a good principle to follow or not, but in general I think it worth at least some consideration when designing class hierarchies.
In a class hierarchy, applying this principle means that a subclass must not
change the expected behavior of its superclass. This is more than a
syntactic constraint: both AdCampaign
and NuclearMissile
might
reasonably have a method named launch
, but those two methods are not
semantically equivalent and therefore don’t really satisfy the LSP.
Substitutability can be enforced using Design by Contract. But even if you don’t want to go that far, it’s worth looking at the DbC rules for subtype contracts:
-
Preconditions cannot be strengthened in a subtype. That is, the subtype cannot impose stricter requirements on inputs than the supertype does.
-
Postconditions cannot be weakened in a subtype. That is, the subtype must make guarantees about the output that are at least as strong as those made by the supertype.
-
Invariants of the supertype must be preserved in a subtype. That is, if the supertype guarantees that some conditions must always be true, then the subtype must also make the same guarantees.
Thinking about these rules will help you decide if you’re violating LSP or not.
In real code, tradeoffs are often necessary, but it’s worth thinking
about the LSP when designing a class hierarchy. If subclass methods
are doing something radically different than the superclass, or if you
have to override an inherited method in order to raise an exception
saying, “This method isn’t appropriate for this class,” there’s a
smell there and it’s worth rethinking your approach. You may decide
to keep things as-is, but do it thoughtfully after considering other
options. Even Smalltalk’s class hierarchy has some methods that send
self shouldNotImplement
because the designers decided that, on
balance, that was the best approach.