Liskov Substitution Principle (SOLID 3/6)


Liskov substitution principle (LSP) has the most difficult definition of five SOLID principles. Especially if we look at the original definition which is practically mathematics:

Subtype Requirement: Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type where is a subtype of T. (Barbara Liskov)

That is something I can’t figure out, especially that it has something to do with object-oriented programming. Robert C. Martin has modified it to a more user-friendly version:

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

But this doesn’t either make any sense of what LSP really is. Luckily LSP is much easier in practice. I will start by violating LSP because that is easier than to start by explaining what it is.

Example: Violating LSP

Consider we have an IBird interface with a Fly method:

public interface IBird
  void Fly();

But when we try to code a Penguin class, we can’t implement the Fly method:

public class Penguin : IBird
  public void Fly()
    throw new NotSupportedException("Penguins can't fly");

This is a quite typical violation of LSP. This was a really simple example but think if we have a good class and we want to extend its behavior. We inherit it (like Penguin above) and code something more. But then we realize that something that we inherited doesn’t fit anymore and we throw NotSupportedException.

LSP is often violated by attempts to remove features. (Mark Seemann)

(This example is based on Tom Dalling’s blog post about LSP and Mark Seemann’s Pluralsight course about SOLID principles.)

Easier Definition

LSP defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. (Thorben Janssen)

Now if we look at the IBird and Penguin example again with this more reasonable definition, we will understand what LSP is. I will explain how the example violated this.

If we have the following method:

public void LetThemFlyAway(IBird[] birds)
  foreach (var bird in birds)

And we have a Penguin object in birds array it will throw a NotSupportedException. Penguin object, which is the subclass of IBird, will break the application. If IBird and Penguin were coded according to LSP it wouldn’t throw an exception.

Practically what LSP says is that if we use superclass in our code, we should be able to replace it with its subclass, an application will still run successfully.

LSP requires the objects of your subclasses to behave in the same way as the objects of your superclass. (Thorben Janssen)

Desing by Contract, and We Will Follow LSP

If it feels difficult to write code that follows LSP, try to follow design by contract. In brief (and little simplified) design by contract means that methods have:

  • Preconditions for input parameters and
  • postconditions for output parameters.

For input parameters, this means that subclass has to accept the same input parameters in the overridden method as superclass accepts in its method. We can make validation looser in subclasses but we are not allowed to make it more strict. It would violate LSP like previous Penguin example and throw an exception if some subclass won’t accept some input that superclass accepts.

We should think output parameters as a return value. The rule is basically the same as with the inputs: the return value of subclass’ method should follow the same rules as the return value of superclass’ method. Unlike with inputs we are not allowed to loosen the rules with outputs. But we can make them more strict if we want. Loosening output requirements also would violate LSP. Think that we have a superclass with the following method:

public class MySuperClass
  public virtual int GetValue()
    var random = new Random();
    return random.Next(1, 10);    // returns 1...9

And then we have a method that uses it to get the sum of numbers:

public int CalculateSumOfValues(MySuperClass[] myClasses)
  int sum = 0;
  foreach (var myClass in myClasses)
    sum += myClass.GetValue();
  return sum;

Remember that MySuperClass expects that return value of GetValue method is between 1 and 9. With three input objects, the return value of CalculateSumOfValues method should be a value between 3 and 27. This is the postcondition contract of the CalculateSumOfValues method. If we now create a subclass that violates design by contract and loosens return value:

public class MySubClass : MySuperClass
  public override int GetValue()
    var random = new Random();
    // Returns 0...9 even if design by contract says should return 1..9.
    return random.Next(0, 10);

Now if we call CalculateSumOfValues method with three MySubClass objects, it will return a value between 0 and 27. That breaks the postcondition contract of the CalculateSumOfValues method which says that the return value should be between 3 and 27. And we have also violated LSP! So if we follow design by contract, we can’t loosen postcondition requirements.

But vice versa we can make return values more strict. For example, if we change MySubClass‘s GetValue method to return a value between 2 and 8 it wouldn’t be a problem. Because then CalculateSumOfValues would return a value between 6 and 24 and that fits into return value’s requirement to be between 3 and 27.

If we follow these two rules when overriding methods in a subclass, our code probably follows LSP.

Relationship With the Open/Closed Principle

While writing this blog post I have been thinking that isn’t LSP practically same as the open/closed principle (OCP)? My opinion is that they are quite similar but they underline different things. OCP is more about extending classes and LSP is more about using objects. If you want to read a deep analysis why LSP and OCP are not same I suggest you read this question in Software Engineering StackExchange.

What Makes LSP Important?

At first glance, LSP seems little difficult and unpractical. The definition isn’t easy to understand. And even if we see code example it is still not easy to understand. It needs some time to understand it.

How can it be so important that it has its own letter in SOLID principles? The reason is that it makes it more easy to use objects because we can trust that they behave as they should. If our code follows LSP, we can change to use any subclass of the given superclass and be sure that our code still works as it is supposed to. This is because of LSP guarantees that every object of subclasses behaves just like superclass.

Sources and Influences

Blog Posts in This Series

5 thoughts on “Liskov Substitution Principle (SOLID 3/6)

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s