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.