L for Liskov Substitution Principle

Coding

L for Liskov Substitution Principle

Derived classes must be substitutable for their base classes.
Robert C. Martin

Welcome back to the series on SOLID. By now, I’ll assume you’ve read my earlier post on the Open/Closed Principle. This principle is quite an obvious principle. I’m going to use the time-and-time-again Duck example below. Think: If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck. That is unless you break the Liskov Substitution Principle. With this principle, you must be able to substitute any object for its base type. What do I mean? Let’s say you have a duck base class. You can add a Mallard Duck, a Marbled Duck, and a Rubber Duck. But – a rubber duck can’t fly. It’ll break your base class implementation of a Duck. You’ll see more below.

Breaking the principle

So, as I explained above: We’ll start by using a Duck base class. In this base class, we’ll have a method Quack(). We’ll also add a few duck types.

public abstract class Duck
{
    public abstract void Quack();
}

public class MallardDuck : Duck
{
    public override void Quack()
    {
        System.Console.WriteLine("Quack!");
    }
}

public class MarbledDuck : Duck
{
    public override void Quack()
    {
        System.Console.WriteLine("Quack!");
    }
}

public class RubberDuck : Duck
{
    public override void Quack()
    {
        System.Console.WriteLine("Squeak!");
    }
}

But, you realise that Ducks also Swim, right? I mean they should swim. So far, so good.

public abstract class Duck
{
    public abstract void Quack();
    public abstract void Swim();
}

public class MallardDuck : Duck
{
    public override void Quack()
    {
        System.Console.WriteLine("Quack!");
    }

    public override void Swim()
    {
        System.Console.WriteLine("Swimming!");
    }
}

public class MarbledDuck : Duck
{
    public override void Quack()
    {
        System.Console.WriteLine("Quack!");
    }

    public override void Swim()
    {
        System.Console.WriteLine("Swimming!");
    }
}

public class RubberDuck : Duck
{
    public override void Quack()
    {
        System.Console.WriteLine("Squeak!");
    }

    public override void Swim()
    {
        System.Console.WriteLine("Floating!");
    }
}

Finally, you realise that Ducks should also fly. Do you know a duck that can’t fly?

public abstract class Duck
{
    public abstract void Quack();
    public abstract void Swim();
    public abstract void Fly();
}

public class MallardDuck : Duck
{
    public override void Quack()
    {
        System.Console.WriteLine("Quack!");
    }

    public override void Swim()
    {
        System.Console.WriteLine("Swimming!");
    }

    public override void Fly()
    {
        System.Console.WriteLine("Flying!");
    }
}

public class MarbledDuck : Duck
{
    public override void Quack()
    {
        System.Console.WriteLine("Quack!");
    }

    public override void Swim()
    {
        System.Console.WriteLine("Swimming!");
    }

    public override void Fly()
    {
        System.Console.WriteLine("Flying!");
    }
}

public class RubberDuck : Duck
{
    public override void Quack()
    {
        System.Console.WriteLine("Squeak!");
    }

    public override void Swim()
    {
        System.Console.WriteLine("Floating!");
    }

    public override void Fly()
    {
        throw new Exception("Rubber ducks can't fly!");
    }
}

Uh oh! A Rubber Duck can’t fly. Let’s use this principle: Subtypes must be substitutable for their base types. Can we substitute RubberDuck for a Duck? No. Because it’ll throw an exception. We definitely don’t want that. So how do we fix this code? Should we fix it?

Fix using the principle

Well, to be honest, a Rubber Duck shouldn’t be a Duck. Full Stop. If we really wanted to fix this, we can use the next principle to take out the Duck abstraction. We can have an interface for Quack, Swim, and Fly. We won’t have a Duck base class anymore, but we’ll have ducks who can do all three and ducks who can only do two.

public interface IFly
{
    void Fly();
}

public interface ISwim
{
    void Swim();
}

public interface IQuack
{
    void Quack();
}

public class MallardDuck : IFly, ISwim, IQuack
{
    public void Quack()
    {
        System.Console.WriteLine("Quack!");
    }

    public void Swim()
    {
        System.Console.WriteLine("Swimming!");
    }

    public void Fly()
    {
        System.Console.WriteLine("Flying!");
    }
}

public class MarbledDuck : IFly, ISwim, IQuack
{
    public void Quack()
    {
        System.Console.WriteLine("Quack!");
    }

    public void Swim()
    {
        System.Console.WriteLine("Swimming!");
    }

    public void Fly()
    {
        System.Console.WriteLine("Flying!");
    }
}

public class RubberDuck : ISwim, IQuack
{
    public void Quack()
    {
        System.Console.WriteLine("Squeak!");
    }

    public void Swim()
    {
        System.Console.WriteLine("Floating!");
    }
}

L for Liskov Substitution Principle

I hope this was a little help explaining the principle. Ducks will be ducks, but a rubber duck is not a duck. Remember that you should be able to substitute your subclass for you base class at any point and we should expect it to do what’s expected. If it ends up doing something unexpected, then you’re probably breaking the Liskov substitution principle.

Republished on Medium.

About the author

Alex Aitken

Alex Aitken

Alex is a software engineering leader focused on AI, data, and product engineering at Airwallex. His opinions are his own.

1 comment

Leave a comment

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.