Object-oriented – How to verify the Liskov substitution principle in an inheritance hierarchy

design-principlesinheritanceliskov-substitutionobject-orientedsolid

Inspired by this answer:

Liskov Substitution Principle
requires

that

  • Preconditions cannot be strengthened in a subtype.
  • Postconditions cannot be weakened in a subtype.
  • Invariants of the supertype must be preserved in a subtype.
  • History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Since
    subtypes may introduce methods that are not present in the supertype,
    the introduction of these methods may allow state changes in the
    subtype that are not permissible in the supertype. The history
    constraint prohibits this.

I was hoping if someone would post a class hierarchy that violates these 4 points and how to solve them accordingly.
I'm looking for an elaborate explanation for educational purposes on how to identify each of the 4 points in the hierarchy and the best way to fix it.

Note:
I was hoping to post a code sample for people to work on, but the question itself is about how to identify the faulty hierarchies 🙂

Best Answer

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.

Related Topic