15

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 :)

1

1 Answer 1

18

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.

12
  • Very good and subtle example. +1. What you could do, is make Enlarge a method of the Rectangle class and override it in the Square class. Oct 17, 2012 at 12:12
  • @marco-fiset: I would rather see Square and Rectangle decoupled, Square with only one dimension, but each implementing IResizable. It's true that if there was a Draw method, they would be similar, but then I'd rather have them both encapsulate a RectangleDrawer class, which includes the common code.
    – pdr
    Oct 17, 2012 at 12:31
  • 1
    I don't think that this is a good example. The problem is that a square has no width or height. It just has a length of it's sides. The problem wouldn't be there if width and height were only readable, but they are writable in this case. When introducing modifiable state it is always a lot more difficult to maintain LSP. Oct 17, 2012 at 13:41
  • @pdr Thanks for the example, but concerning the 4 conditions I mentioned in my post which part of the Square class violates them?
    – Songo
    Oct 17, 2012 at 19:02
  • 1
    @Songo: It's the History Constraint. Better explained here: blackwasp.co.uk/LSP.aspx "By their nature, subclasses include all of the methods and properties of their superclasses. They may also add further members. The history constraint says that new or modified members should not modify the state of an object in a manner that would not be permitted by the base class. For example, if the base class represents an object with a fixed size, the subclass should not permit this size to be modified."
    – pdr
    Oct 17, 2012 at 20:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.