This is part three of a five part series about SOLID class design principles by Robert C. Martin. The SOLID principles focus on achieving code that is maintainable, robust, and reusable. In this post, I will discuss the Liskov Substitution Principle.

The Liskov Substitution Principle (LSP): functions that use pointers to base classes must be able to use objects of derived classes without knowing it.

When first learning about object oriented programming, inheritance is usually described as an "is a" relationship. If a penguin "is a" bird, then the Penguin class should inherit from the Bird class. The "is a" technique of determining inheritance relationships is simple and useful, but occasionally results in bad use of inheritance.

The Liskov Substitution Principle is a way of ensuring that inheritance is used correctly.

Tom's Penguin Problem

The classic example of the "is a" technique causing problems is the circle-elipse problem (a.k.a the rectangle-square problem). However, I'm going use penguins.

First, consider an application that shows birds flying around in patterns in the sky. There will be multiple types of birds, so the developer decides to use the Open Closed Principle to "close" the code to the addition of new types of birds. To do this, the following abstract Bird base class is created:

class Bird {
public:
    virtual void setLocation(double longitude, double latitude) = 0;
    virtual void setAltitude(double altitude) = 0;
    virtual void draw() = 0;
};

Version one of BirdsFlyingAroundApp is a huge success. Version two adds another 12 different types of birds with ease, and is also a success. Hooray for the Open Closed Principle. However, version three of the app is required to support penguins. The developer makes a new Penguin class that inherits from the Bird class, but there is a problem:

void Penguin::setAltitude(double altitude)
{
    //altitude can't be set because penguins can't fly
    //this function does nothing
}

If an override method does nothing or just throws an exception, then you're probably violating the LSP.

When the app is run, all the flying patterns look wrong because the Penguin objects ignore the setAltitude method. The penguins are just flopping around on the ground. Even though the developer tried to follow the OCP, they failed. Existing code must be modified to accommodate the Penguin class.

While a penguin technically "is a" bird, the Bird class makes the assumption that all birds can fly. Because the Penguin subclass violates the flying assumption, it does not satisfy the Liskov Substitution Principle for the Bird superclass.

Why Violating The LSP is Bad

The whole point of using an abstract base class is so that, in the future, you can write a new subclass and insert it into existing, working, tested code. This is the essence of the Open Closed Principle. However, when the subclasses don't adhere properly to the interface of the abstract base class, you have to go through the existing code and account for the special cases involving the delinquent subclasses. This is a blatant violation of the Open Closed Principle.

For example, take a look at this fragment of code:

//Solution 1: The wrong way to do it
void ArrangeBirdInPattern(Bird* aBird)
{
    Pengiun* aPenguin = dynamic_cast<Pengiun*>(aBird);
    if(aPenguin)
        ArrangeBirdOnGround(aPenguin);
    else
        ArrangeBirdInSky(aBird);
}

The LSP says that the code should work without knowing the actual class of the Bird object. What if you want to add another type of flightless bird, like an emu? Then you have to go through all your existing code and check if the Bird pointers are actually Emu pointers. You should be wrinkling your nose at the moment, because there is definitely a code smell in the air.

Two Possible Solutions

We want to be able to add the Penguin class without modifying existing code. This can be achieved by fixing the bad inheritance hierarchy so that it satisfies the LSP.

One not-so-great way of fixing the problem is to add a method to the Bird class named isFlightless. This way, at least additional flightless bird classes can be added without violating the OCP. This would result in code like so:

//Solution 2: An OK way to do it
void ArrangeBirdInPattern(Bird* aBird)
{
    if(aBird->isFlightless())
        ArrangeBirdOnGround(aBird);
    else
        ArrangeBirdInSky(aBird);
}

This is really a band-aid solution. It hasn't fixed the underlying problem. It just provides a way to check whether the problem exists for a particular object.

A better solution would be to make sure flightless bird classes don't inherit flying functionality from their superclasses. This could be done like so:

//Solution 3: Proper inheritance
class Bird {
public:
    virtual void draw() = 0;
    virtual void setLocation(double longitude, double latitude) = 0;
};

class FlightfulBird : public Bird {
public:
    virtual void setAltitude(double altitude) = 0;
};

I don't think the English language has a word that means the opposite of "flightless", but let's be Shakespearian and invent the word "flightful" to fill the position. In the above solution the Bird base class does not contain any flying functionality, and the FlightfulBird subclass adds that functionality. This allows some functions to be applied to both Bird and FlightfulBird objects; drawing for example. However, the Bird objects, which may be flightless, can not be shoved into functions that take FlightfulBird objects.