Liskov Substitution Principle

Another principle of object-oriented software design, the L in SOLID, the Liskov Substitution Principle! But first a little background and some theory (feel free to skip right to the practical part of the post). The principle is called after Barbara Liskov, who initially introduced it in 1987. Prof. Liskov first defined it like this

What is wanted here is something like the following substitution property: If
for each object O1 of type S there is an object O2 of type T such that for all
programs P defined in terms of T, the behavior of P is unchanged when O1 is
substituted for O2 then S is a subtype of T.

This definition is a little other way around. It says (at least I read it like this), “if you can substitute each O1 with some O2, it’s safe to say, that S is a subtype of T”. But there are cases (not that rare) in which S is subtype of T, but the objects aren’t substitutable (and that’s bad). It was later rephrased and published in a paper by Barbara Liskov and Jeannette Wing in 1994. The formulation changed to

Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

This is a little better. Now it says that if something works for objects of type T, it should work for objects of type S as well in case that S is a subtype of T. And that’s the Liskov Substitution Principle. Robert C. Martin later described it in one of his articles like this

Functions that use pointers or references to base
Classes must be able to use objects of derived classes
Without knowing it.

Now, that’s how most of us like our software principles, right? No weird letters or signs — straight-forward and easy to understand. I like the theory though :-). Anyway, how is this good in practice in the regular everyday coding? Let’s have a look at some examples. One of the most typical violation is Square-Rectangle class hierarchy.

One would think that having a Square class that is a subclass of Rectangle could be a good idea, right? The relationship represented Square is a Rectangle seems to work, so let’s do it.

class Rectangle
{
    int width;
    int height;

    public:
        int getWidth() { return width; }
        int getHeight() { return height; }

        virtual void setWidth(int value) { width = value; }
        virtual void setHeight(int value) { height = value; }
};

Pretty straight-forward declaration. The square is a rectangle which width and height are equal. So we redefine the set methods.

class Square : public Rectangle
{
    public:
        void setWidth(int value)
        { width = value; height = value; }
        void setHeight(int value)
        { width = value; height = value; }
};

This modification will make sure, that our square has always all sides equal. Then consider having a function like this

bool test(Rectangle &rectangle)
{
    rectangle.setWidth(2);
    rectangle.setHeight(3);

    return rectangle.getWidth() * rectangle.getHeight() == 6;
}

This function tests the interface of Rectangle. But what happens when you pass a reference to a Square to it? It will break, because of the side effect of the set methods, that keeps the Squarea square. So, where’s the problem here?

The square is a rectangle, but does not share the same behaviour. And that’s a deal-breaker when it comes to inheritance in software. The LSP clarifies that in OOD the is a relationship applies to public behavior of objects.

Bertrand Meyer also explored the topic in Design by Contract. He states

…when redefining a routine [in a derivative], you may only replace its
precondition by a weaker one, and its postcondition by a stronger one.

Preconditions are something that must be true in order to the method to execute and postconditions are always true after the method has been executed. This rule really helps me when I design something. Basically it says, that you can only reduce the set of preconditions and only extend the set of postconditions. In other words, the new routine cannot require anything more than the original one (but can require even less) and cannot yield anything less then the original one (but can also return something on top of that).

In context with the square-rectangle problem, there were no preconditions, but there was one postcondition to the setHeight() method. The postcondition assumed, that the set method for height won’t change the width (that’s a perfectly justified assumption). And this precondition was broken by the redefined routine of Square.

Inheritance is very powerful and important concept in object-oriented design. But it’s also easy to get it dead wrong. The Liskov Substitution Principle should make you thing more about the relationship, when you create one and help avoid eventual oh-moment coming your way.

Sources

Advertisements

2 comments

  1. Pingback: SOLID Object-Oriented Design « linux-well
  2. Pingback: Interface Segregation Principle in Software Design « linux-well

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s