The Liskov Substitution Principle (LSP) states that child class objects should be able to replace parent class objects without compromising application integrity. What this means essentially, is that we should put an effort to create such derived class objects which can replace objects of the base class without modifying its behavior. If we don’t, our application might end up being broken.

Does this make sense to you?

To make things clear, we are going to use a simple „Sum Calculator“ example, which will help us to understand how to implement the LSP better.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

To download the source code for this project, check out the Liskov Substitution Principle Project Source Code.

To read about other SOLID principles, check out our SOLID Principles page.

Initial Project

In this example, we are going to have an array of numbers and a base functionality to sum all the numbers from that array. But let’s say we need to sum just even or just odd numbers.

How would we implement that? Let’s see one way to do it:

public class SumCalculator
{
    protected readonly int[] _numbers;

    public SumCalculator(int[] numbers)
    {
        _numbers = numbers;
    }

    public int Calculate() => _numbers.Sum();
}
public class EvenNumbersSumCalculator: SumCalculator
{
    public EvenNumbersSumCalculator(int[] numbers)
        :base(numbers)
    {
    }

    public new int Calculate() => _numbers.Where(x => x % 2 == 0).Sum();
}

Now if we test this solution, whether we calculate the sum of all the numbers or the sum of just even numbers, we are going to get the correct result for sure:

class Program
{
    static void Main(string[] args)
    {
        var numbers = new int[] { 5, 7, 9, 8, 1, 6, 4 };

        SumCalculator sum = new SumCalculator(numbers);
        Console.WriteLine($"The sum of all the numbers: {sum.Calculate()}");

        Console.WriteLine();

        EvenNumbersSumCalculator evenSum = new EvenNumbersSumCalculator(numbers);
        Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}");
    }
}

The result is:

LSP not implemented - Liskov Substitution Principle

Creating a Better Solution

As we can see, this is working just fine. But what is wrong with this solution then?

Why are we trying to fix it?

Well, as we all know, if a child class inherits from a parent class, then the child class is a parent class. Having that in mind, we should be able to store a reference to an EvenNumbersSumCalculator as a SumCalculator variable and nothing should change. So, let’s check that out:

SumCalculator evenSum = new EvenNumbersSumCalculator(numbers);
Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}");

Wrong result - Liskov Substitution Principle

As we can see, we are not getting the expected result because our variable evenSum is of type SumCalculator which is a higher order class (a base class). This means that the Count method from the SumCalculator will be executed. So, this is not right, obviously, because our child class is not behaving as a substitute for the parent class.

Luckily, the solution is quite simple. All we have to do is to implement small modifications to both of our classes:

public class SumCalculator
{
    protected readonly int[] _numbers;

    public SumCalculator(int[] numbers)
    {
        _numbers = numbers;
    }

    public virtual int Calculate() => _numbers.Sum();
}

public class EvenNumbersSumCalculator: SumCalculator
{
    public EvenNumbersSumCalculator(int[] numbers)
        :base(numbers)
    {
    }

    public override int Calculate() => _numbers.Where(x => x % 2 == 0).Sum();
}

As a result, when we start our solution, everything works as expected and the sum of even numbers is 18 again.

So, let’s explain this behavior.  If we have a child object reference stored in a parent object variable and call the Calculate method, the compiler will use the Calculate method of the parent class. But right now because the Calculate method is defined as „virtual“ and is overridden in the child class, that method in the child class will be used instead.

Implementing the Liskov Substitution Principle

Still, the behavior of our derived class has changed and it can’t replace the base class. So we need to upgrade this solution by introducing the Calculator abstract class:

public abstract class Calculator
{
    protected readonly int[] _numbers;

    public Calculator(int[] numbers)
    {
        _numbers = numbers;
    }

    public abstract int Calculate();
}

Then we have to change our other classes:

public class SumCalculator : Calculator
{
    public SumCalculator(int[] numbers)
        :base(numbers)
    {
    }

    public override int Calculate() => _numbers.Sum();
}
public class EvenNumbersSumCalculator: Calculator
{
    public EvenNumbersSumCalculator(int[] numbers)
       :base(numbers)
    {
    }

    public override int Calculate() => _numbers.Where(x => x % 2 == 0).Sum();
}

Excellent. Now we can start making calls towards these classes:

 class Program
{
    static void Main(string[] args)
    {
        var numbers = new int[] { 5, 7, 9, 8, 1, 6, 4 };

        Calculator sum = new SumCalculator(numbers);
        Console.WriteLine($"The sum of all the numbers: {sum.Calculate()}");

        Console.WriteLine();

        Calculator evenSum = new EvenNumbersSumCalculator(numbers);
        Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}");
    }
}

We will again have the same result, 40 for all the numbers and 18 for the even numbers. But now, we can see that we can store any subclass reference into a base class variable and the behavior won’t change which is the goal of LSP.

What We Gain By Implementing the LSP

By implementing the LSP, we are keeping our functionality intact and still having our subclasses act as a substitute to a base class.

Also, we encourage the code reusability by implementing the LCP and having better project maintenance as well.

Conclusion

We can see that implementing the LSP is not that complicated but just the opposite. Most of us probably already implemented this principle many times in our code without knowing its name because in the object-oriented world Polymorphism is quite a big thing.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!