The Liskov Substitution Principle

by Curtis Poe

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.


6 Comments

Joe
2008-02-07 13:31:40
Subclasses can have more restrictive arguments to methods, it just that the object still has to act like the super class. For instance, there's an Integer class and a PositiveInteger subclass. The subclass obviously has to restrict it's arguments since it only deals with a subset of the numbers, but you should still be able to substitute a PositiveInteger object for an Integer object. Their values have the same properties, although the classes deal with different sets of them. That's the point of many subclasses, in fact. :)
Thiago
2008-02-07 15:25:49
Hey, you already heard about Contract Programming, didn't you ?
In Contracts, when you override a method, you can allow more values to be accepted as input parameters. But, you must restrict the return value or output values of the method.
I mean, PreConditions don't need to be satisfied but all the PostConditions should be satisfied.


[code]
Time::Set(int hour, int minute, int second)
{
// PRECONDITIONS
assert( 0 <= hour && hour < 24 );
assert( 0 <= minute && minute < 60 );
assert( 0 <= second && second < 60 );


_hour = hour;
_minute = minute;
_second = second;


// POSTCONDITIONS
// None in this case (sorry) :)
}


// (SpentTime is derived from Time)
SpentTime::Set(int hour, int minute, int second)
{
// PRECONDITIONS are weaker
assert( 0 <= hour );
assert( 0 <= minute );
assert( 0 <= second );


_hour = hour;
_minute = minute;
_second = second;
}
[/code]

Daniel Berger
2008-02-08 14:02:58
Is a Path a kind of String? Yes. Can I choose to implement a Path class as a subclass of String.


If we follow the LSP then the answer to the second question is no and I must delegate all string-like methods. Why? Using Ruby as my example, consider the String#+ method versus our theoretical Path#+ instance method. With plain strings 'foo' + 'bar' results in 'foobar', but Path.new('foo') + Path.new('bar') results in 'foo/bar'.


Then consider that String + Path results in a String, but Path + String results in a Path.


If I've violated LSP (and I'm not entirely sure that I have) then I don't care, because I'm always going to get the expected behavior - a string when I want one or a Path when I want that, depending on what context I've used it.

Robert Fischer
2008-02-09 08:25:17
I had a conversation on this a while back on my blog (click my name to read more about it), and there are all kinds of issues that are being whitewashed over here.


The biggest is that you act as though the only difference two methods might have are the values you accept and the values you produce. But since you're talking about a language with side effects, that means that your side effects (a.k.a. behaviors) have to be the same -- you can add new side effects, but you can't replace them. This basically means that you can add new functionality, but you can't ever remove/replace existent functionality.


Another problem is what that definition means when it says "a property provable about". Most code isn't written to look like a mathematical proof -- most of it doesn't even really have a specific communication of the API. So developers usually don't have the foggiest idea what is "provable about" their code, they just know how they're using their code. And that gets into a lot of sticky situations.


For instance (and I get into this more on my blog), consider a 2D matrix class, and a symmetric 2D matrix subclass. Is it "provable about" the matrix class that assigning [a][b] doesn't change any other element on the matrix? If so, then the symmetric 2D matrix subclass is a violation of the LSP. If not, then it is a just fine subclass. Of course, in most code bases, there's no clear answer to that question: the answer is really whether or not someone has already written code that depends on that part of the contract being true.


That's a just fine theory about classes and subclasses, but it's pretty limited in practice beyond the implementation suggestions you've laid out in this article.


Of course, it's no surprise that the theory falls a bit short in practice. Theory is a lot closer to practice in theory than it is in practice. ;D

Mike
2008-02-16 13:07:32
LSP is good at exposing faulty abstractions like the path is a string example provided above.


A path is not a string, it is a path. A string is just representation. If you need to rely on a path you should make something called path which holds parts of the path. You shouldn't rely on a crappy regular expression to verify if something is a path, do the real thing.

Runrig
2008-02-26 10:00:43
What? No "can"?

for twin in wonder_twins()
for power in twin.powers()
if power.can('activate')
power.activate()

Although I would think that it's the twins that activate the power and not the power that activates itself:

for twin in wonder_twins()
for power in twin.powers()
if ! twin.is_activated(power)
twin.activate(power)

And if some super-villain somehow disables activation of some power, maybe:

for twin in wonder_twins()
for power in twin.powers()
if twin.is_activatable(power) and ! twin.is_activated(power)
twin.activate(power)

Hmm, I've thought way too much about this :-)