«

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 Square a 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