It's a lot more simple than that quote makes it sound, accurate as it is.
When you look at an inheritance hierarchy, imagine a method which receives an object of the base class. Now ask yourself, are there any assumptions that someone editing this method might make which would be invalid for that class.
For example originally seen on Uncle Bob's site (broken link removed):
public class Square : Rectangle
{
public Square(double width) : base(width, width)
{
}
public override double Width
{
set
{
base.Width = value;
base.Height = value;
}
get
{
return base.Width;
}
}
public override double Height
{
set
{
base.Width = value;
base.Height = value;
}
get
{
return base.Height;
}
}
}
Seems fair enough, right? I've created a specialist kind of Rectangle called Square, which maintains that Width must equal Height at all times. A square is a rectangle, so it fits with OO principles, doesn't it?
But wait, what if someone now writes this method:
public void Enlarge(Rectangle rect, double factor)
{
rect.Width *= factor;
rect.Height *= factor;
}
Not cool. But there's no reason that the author of this method should have known there could be a potential problem.
Every time you derive one class from another, think about the base class and what people might assume about it (such as "it has a Width and a Height and they would both be independent"). Then think "do those assumptions remain valid in my subclass?" If not, rethink your design.
You aren't just breaking LSP here, you're breaking the basic concept of inheritance. If you have a format(Argument $arg)
in your base type, then you must have something which can match it in the derived type.
Consider that if your example were allowed, what would happen if you attempted to pass a string to OutputNumeric::format
? If you had specific type information and you knew you were passing it to an OutputNumeric, it would be a type error. If you didn't and you were passing it to an Output, it wouldn't be a type error, but where could it dispatch to?
This isn't so much a violation of LSP as it is a violation of the basic contract set out by that superclass, and you can't go around violating those or suddenly your compiler can get into situations where it can't tell you that you're wrong, but it also has nowhere to dispatch the function to!
What could be a violation of LSP in this case would be if you had a NumericOutput which accepted any natural number, but also a subclass which would fail on any number higher than n
. The class's contract (or "interface") hasn't changed, but the preconditions have.
Best Answer
I think it's stated very well in that question which is one of the reasons that was voted so highly.
Imagine if you will:
In this method, occasionally the .Close() call will blow up, so now based on the concrete implementation of a derived type you have to change the way this method behaves from how this method would be written if Task had no subtypes that could be handed to this method.
Due to liskov substitution violations, the code that uses your type will have to have explicit knowledge of the internal workings of derived types to treat them differently. This tightly couples code and just generally makes the implementation harder to use consistently.