• 4 hours
  • Easy

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/28/24

“L” for the Liskov Substitution Principle

Liskov Substitution Principle (LSP)

The Liskov substitution principle (LSP) was named after Barbara Liskov, the computer scientist who defined it.

Barbara Liskov (source: Wikimedia Commons)
Barbara Liskov (source: Wikimedia Commons)

Liskov introduced the Liskov substitution principle in her conference keynote talk, “Data Abstraction,” in 1987. A few years later, she published a paper with Jeannette Wing in which they defined the principle as the following: 

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

That definition is a bit too scientific. What does it mean?

The definition means that child class objects should be able to replace parent class objects without breaking the integrity of the application.

Any code calling methods on objects of a specific type should continue to work when those objects get replaced with instances of a subtype.

What’s a subtype?

A subtype can be either a class extending another class or a class implementing an interface.

Let’s see this in action. First, let’s look at a simple calculator example:

public class SumCalculator
{
    protected readonly int[] _numbers;
 
    public SumCalculator(int[] numbers)
    {
        _numbers = numbers;
    }
 
    public int Calculate() => _numbers.Sum();
}

Above is a  SumCalculator  class that, when instantiated, requires an array of integers. When  Calculate  is invoked, it will return the sum of all the integers in the array.

Next, inherit the  SumCalculator  class which acts as a subtype for the  EvenNumbersSumCalculator  class:

public class EvenNumbersSumCalculator: SumCalculator
{
    public EvenNumbersSumCalculator(int[] numbers)
        :base(numbers)
    {
    }
 
    public new int Calculate() => _numbers.Where(x => x % 2 == 0).Sum();
}

Now, let’s utilize both classes in the console application:

public class Program
{
    static void Main(string[] args)
    {
        int[] 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()}”);
 
        EvenNumbersSumCalculator evenSum = new EvenNumbersSumCalculator(numbers);
        Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}");
    }
}

The result is:

The sum of all the numbers: 40.
The sum of all the even numbers: 18.

Great! That’s all correct!

If that’s correct, then what’s wrong?

With the LSP, a child class should be able to replace its parent class. We should be able to store a reference to  EvenNumbersSumCalculator  as a  SumCalculator  and nothing should change.

Let’s change the  evenSum  variable within the  Main  method from above to the following:

SumCalculator evenSum = new EvenNumbersSumCalculator(numbers);

Now, the output is:

The sum of all the even numbers: 40.

What? 🤔

The reason it returned 40 instead of 18 is the variable  evenSum  is of type  SumCalculator, which is acting as a base class. It means that the  Calculate  method from  SumCalculator  will be executed instead.

It is incorrect because the child class,  EvenNumbersSumCalculator, is not acting as a substitute for its parent class,  SumCalculator.

Fix that by first implementing a small modification to both classes using the  virtual  and  override  modifiers. Remember, the  virtual  modifier marks a method, property, indexer, or event overridable, so a derived class can use the  override  modifier.

In the  SumCalculator  class, change the  Calculate  to this: 

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

And then override it in the  Calculate  method in the  EvenNumbersSumCalculator  class:

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

The  Calculate  method in the child class now overrides the parent class. So, when you rerun the program using this line:

SumCalculator evenSum = new EvenNumbersSumCalculator(numbers);

It will return 18 instead of 40 because the  Calculate  method inside  EvenNumbersSumCalculator  is overriding the parent,  SumCalculator’s  calculate.

Great, we’re done, right?

Well, no. Unfortunately, the behavior of the derived class,  EvenNumbersSumCalculator, has changed, and it can’t replace the base class,  SumCalculator.

In the last chapter, you learned about contracts, which are abstract classes and interfaces. I used an interface for an example of the open/closed principle. For this example, I am going to use an abstract class to create a better base class in which abstract classes are normally used. 

public abstract class Calculator
{
    protected readonly int[] _numbers;
 
    public Calculator(int[] numbers)
    {
        _numbers = numbers
    }
 
    public abstract int Calculate();
}

Hey, doesn't that look like the  SumCalculator  class?

Yes, because I was treating the  SumCalculator  class as a base class. But, I changed the  Calculate()  method by adding the abstract modifier, which is overridable. Remember, in the last chapter, I said that an abstract class has to have at least one abstract method.

So, now let’s have the  SumCalculator  and the  EvenNumbersSumCalculator  inherit the new base class,  Calculator :

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();
}

Update the console application:

public class Program
{
    static void Main(string[] args)
    {
        int[] 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()}");
 
        Calculator evenSum = new EvenNumbersSumCalculator(numbers);
        Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}");
    }
}

The result is the same:

The sum of all the numbers: 40.
The sum of all the even numbers: 18.

As you can see, you can store any subclass reference (SumCalculator,  EvenNumbersSumCalculator) into a base class variable (Calculator sum,  Calculator evenSum) and the behavior won’t change.

The functionality is still intact, and the subclasses continue to act as a substitute to a base class.

Let’s Recap!

  • Child class objects should be able to replace parent class objects.

  • Subtypes are either a class extending another class, like an abstract class, or a class implementing an interface.

  • A common use of an abstract class is as a base class.

  • Calling methods on objects of a specific type should continue to work when those objects get replaced with instances of a subtype.

Now that we’ve covered the “L” principle let’s move on to “I,” interface segregation.

Example of certificate of achievement
Example of certificate of achievement