Uh oh! There’s danger! Time for the Wonder Twins to activate their super powers.
for twin in wonder_twins() for power in twin.powers() power.activate() ... Fatal Error: Subtype "AnimalMagnetism" cannot be activated in class "SaveTheWorld", line ...
Humanity is destroyed because another developer hasn’t heard of the Liskov Substitution Principle. Let’s take a closer look at it.
The Wikipedia article linked to above has this, er, succinct description:
Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T.
The article goes on a bit more to explain what’s going on, but I confess that at first my eyes glazed over for a moment. It’s not that this description is wrong, it’s just so terse that many developers don’t get it.
I recently had a developer tell me “yeah, that’s a nice idea in theory”. And he’s right. It is a nice idea in theory. It’s an even nicer idea when put in practice. Those who understand the Principle might roll their eyes at the programmer’s statement, but he’s actually a good programmer who I’d be happy to work with on any project. It’s just that many programmers learn OO without being exposed to many of its fundamental rules.
In the above pseudo-code, imagine that there is a “Power” abstract class. A Wonder Twin might have an “AnimalMagnetism” implementation of “Power” which lets him get free drinks in nightclubs. Very useful. However, another developer comes along and envisions a weaker subclass of this power, but it’s always on and thus does not need to be activated, so they have the activate() method throw an exception. Here’s one (wrong) way to work with this:
for twin in wonder_twins() for power in twin.powers() if power.type != "AnimalMagnestism" power.activate()
The problem here is that you now have to search through your code for every attempt to activate a power and write a special case for it. Even if this is the only place, you run the risk of updating it every time you add a new power. In fact, though this statement will seem a bit strong for some, the existence of conditionals (if/then) in OO code is something of a code smell. Once I realized this, I found that by going through my OO code and looking at “if” statements, I often found subtle design flaws.
One better method of dealing with this problem may be to simply have the weaker “AnimalMagnetism” subclass ignore calls to the activate() method (simply having a stub method with no body might be sufficient).
If you really feel that a subclass is needed for something, there are some points to remember. If you are extending it, that’s fine. If you’re altering the behavior of an existing method, you must not have tighter restrictions on what arguments can be accepted. In other words, if the number 3 is a valid argument to a method, you shouldn’t suddenly disallow the number 3. You’re going to get frantic 3:00 AM phone calls wondering why the boss can’t order 3 drinks.
The return arguments, if any, must also be considered. You can have tighter restrictions on what you emit, but you must not expand the domain. In other words, if the calling code expects no negative numbers, don’t start returning them or else the calling code’s behavior is likely to be unpredictable.
In other words, any code dealing an object of a type X should never have to know or care whether or not they are dealing with a subtype of X.
Go back and reread the Wikipedia article. If you didn’t understand it before, it might make more sense now. However, remember that the only hard and fast rule is that there are no hard and fast rules. While the Liskov Substitution Principle is incredibly important, sometimes we find ourselves coding into a corner due to deadlines. Just don’t forget to go back and refactor later.
Side note: some argue that Liskov means that you must never override an existing method. I think that’s a little extreme and not what was intended.