C# Liskov Substitution Principle With Examples

In this article, we will learn the C# Liskov Substitution Principle and understand implementation to build scalable and easier to maintain software applications.

csharp liskov substitution principle

Understanding the Liskov Substitution Principle

The Liskov Substitution Principle is one of the five SOLID principles of OOP, each of which addresses a specific characteristic of software design. 

Remember that the Liskov Substitution Principle derives its name from Barbara Liskov, who initially conceptualized it in 1988.

What is the Liskov Substitution Principle?

The Liskov Substitution Principle states that the object of a derived class should be substitutable in place of the base class object without causing bugs or unexpected behaviour in the existing program. 

In simple terms, LSP says that if a child class “B” is derived from another base class “A”, then the object of class “B” should be able to replace the object of class “A” without any issue.

Example: Without Liskov Substitution Principle

Let’s consider an example involving a class hierarchy for various bird species. We’ll create a base class Bird and two derived classes Penguin and Eagle. The Bird class has a method Fly() that represents the ability to fly.

public class Bird
{
    public virtual void Fly()
    {
        Console.WriteLine("This bird can fly.");
    }
}

public class Penguin : Bird
{
    public override void Fly()
    {
        Console.WriteLine("Penguins cannot fly.");
    }
}

public class Eagle : Bird
{
    public override void Fly()
    {
        Console.WriteLine("Eagles can fly at great heights.");
    }
}

In this scenario, we have two types of birds: Penguins and Eagles. Both of them are subclasses of the Bird class. 

However, the Penguin subclass modifies the Fly() method to reflect that penguins are unable to fly, whereas the Eagle subclass keeps the default behavior of flying.

Problem Without Liskov Substitution Principle

The issue arises when we try to use the Fly() method without considering the Liskov Substitution Principle. Let’s see what happens:

static void Main(string[] args)
{
    Bird[] birds = { new Bird(), new Penguin(), new Eagle() };

    foreach (var bird in birds)
    {
        bird.Fly();
    }
}

Output:

Liskov Substitution Principle in CSharp

While the above output seems fine, a problem arises when we attempt to treat all birds uniformly without accounting for their specific abilities.

In this case, we expect all birds to have the ability to fly due to their common base class, but the Penguin class undermines this expectation. The code violates the Liskov Substitution Principle because the Penguin class doesn’t honour the behavior of the base class, leading to confusion and potential errors in the code.

Applying the Liskov Substitution Principle

To address this issue, we can restructure our design by either allowing the Penguin class to fly (even if it’s a pretend of swimming instead of flying) or by revising our class hierarchy.

Let’s opt for the second approach and introduce an intermediate classes FlyingBird and SwimmingBird:

using System;

public class Bird
{
    public virtual void PerformAction()
    {
        Console.WriteLine("This bird can perform an action.");
    }
}

public class FlyingBird : Bird
{
    public override void PerformAction()
    {
        Console.WriteLine("This bird can fly.");
    }
}

public class SwimmingBird : Bird
{
    public override void PerformAction()
    {
        Console.WriteLine("This bird can swim.");
    }
}

public class Penguin : SwimmingBird
{
    public override void PerformAction()
    {
        Console.WriteLine("Penguins are great swimmers.");
    }
}

public class Eagle : FlyingBird
{
    public override void PerformAction()
    {
        Console.WriteLine("Eagles can fly at great heights.");
    }
}

class Program
{
    static void Main(string[] args)
    {      
        Bird[] birds = { new Bird(), new Penguin(), new Eagle() };
        foreach (var bird in birds)
        {
            bird.PerformAction();
        }

        Console.ReadKey();
    }

}

Output:

This bird can perform an action.
Penguins are great swimmers.
Eagles can fly at great heights.

Explanation of the Improved Code:

  • We have a base class Bird that defines a virtual method PerformAction(). This method is designed to showcase a general action that birds can perform.
  • We also created two specialized bird categories: FlyingBird and SwimmingBird. These classes inherit from the Bird class and override the PerformAction() method to specify their respective capabilities.
  • The Penguin class is derived from SwimmingBird and further refines the behavior by specifying that penguins are great swimmers.
  • The Eagle class is derived from FlyingBird and specifies that eagles can fly at great heights.
  • In the Main method, we create an array of different bird instances, including instances of the base class and its derived classes.
  • We iterate through the array and call the PerformAction() method for each bird. This demonstrates that each bird class exhibits its unique behavior as intended.

Our code maintains consistent behavior across different bird categories by applying the Liskov Substitution Principle. This approach ensures that substituting derived classes for their base class doesn’t lead to unexpected outcomes, and the code remains more understandable and maintainable.

Key points of the Liskov Substitution Principle (LSP)

Here are the key points of the Liskov Substitution Principle (LSP) explained:

  1. Behavior Preservation: When you create a new type (class) derived from an existing type (base class), the new type should behave in a way consistent with the base type. In other words, you should keep how the base type behaves when you create a new version of it.
  2. Method Signatures: If you’re creating a new type from a base type and you override a method from the base type, the new method in the derived class should have the same name, take the same kind of input (parameters) and provide the same kind of output (return type). This way, when you replace the base type with the derived type, the code that uses these methods doesn’t break.
  3. Exception Handling: If the base type’s methods can throw certain types of errors, the derived type’s methods should also throw the same or closely related errors. This ensures that the code using the base type isn’t surprised by new errors when using the derived type.
  4. Invariants: When dealing with classes and objects, certain conditions or rules are always supposed to be true. These are called invariants. If the base type establishes certain invariants, the derived type should uphold those invariants too. This maintains the expected behavior and properties of the classes even after inheritance.

FAQs: Liskov Substitution Principle

Q1: What is the Liskov Substitution Principle?

The Liskov Substitution Principle, also known as LSP, is a core principle in object-oriented programming. It highlights the importance of ensuring that derived classes can be used as replacements for their base classes without causing any issues with the correctness of the program.

It means that the behavior of the derived class should be the same as the base class rather than contradict it.

Q2: Why is the Liskov Substitution Principle important?

The Liskov Substitution Principle ensures that new classes derived from existing ones can be used seamlessly in place of their base classes. This promotes code reliability, reusability, and maintainability and allows us to extend and modify the software more efficiently.

Q3: How does the Liskov Substitution Principle prevent unexpected behavior?

By following the Liskov Substitution Principle (LSP), derived classes maintain the same behavior and method signatures as their base classes. This prevents surprises when replacing instances of the base class with instances of derived classes, as the expected behavior is maintained.

Q4: What is an example of the Liskov substitution Principle?

Certainly! Consider a scenario with a base class Bird and derived classes Eagle and Penguin. The Liskov Substitution Principle ensures that even though eagles and penguins have different abilities (flying and swimming, respectively), they can be used interchangeably as instances of the base class Bird without causing unexpected issues. This principle promotes the smooth integration of derived classes into existing code.

Q5: What are the benefits of the Liskov Substitution Principle?

By following the Liskov Substitution Principle, you ensure that when you introduce new classes derived from existing ones, they seamlessly fit into your program’s structure without causing surprises or breaking existing functionality.

Articles you might also like:

Shekh Ali
5 2 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments