In this article we are going to discuss Liskov Substitution Principle (LSP) in C#. There are many blogs, articles are available on the internet regarding Liskov Substitution Principle (LSP) but in this particular article I will try to explain to you with as much as simple.
Liskov Substitution Principle (LSP)
The Liskov Substitution Principle (LSP) is one of the five SOLID principles of object-oriented design. It states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
In other words, if S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program (correctness, task performed, etc.).
To understand the Liskov Substitution Principle with an example in C# and how it can be violated, let's look at the following scenario:
Here’s a detailed explanation along with an example of violating and adhering to the OCP in C#:
Violating Liskov Substitution Principle (LSP)
In this example, the Penguin class violates the Liskov Substitution
Principle. According to the principle, a Penguin should be replaceable with any
Bird without affecting the program's correctness. However, since Penguin cannot
fly, calling the Fly method throws an exception, which violates the expectations
set by the Bird class.
using System;
public abstract class Bird
{
public abstract void Fly();
}
public class Parrot: Bird
{
public override void Fly()
{
Console.WriteLine("Parrot is flying");
}
}
public class Penguin : Bird
{
public override void Fly()
{
throw new NotSupportedException("Penguins can't fly");
}
}
public class Program
{
public static void Main()
{
Bird bird1 = new Parrot();
bird1.Fly(); // This works correctly
Bird bird2 = new Penguin();
try
{
bird2.Fly(); // This throws an exception
}
catch (NotSupportedException ex)
{
Console.WriteLine(ex.Message); // Output: Penguins can't fly
}
}
}
Output
In this example:
Correcting the Violation
To correct the violation, we redesign the classes to ensure that subclasses only implement methods they can logically support.
using System;
public interface IBird
{
void MakeSound();
}
public interface IFlyingBird : IBird
{
void Fly();
}
public class Parrot: IFlyingBird
{
public void Fly()
{
Console.WriteLine("Parrot is flying");
}
public void MakeSound()
{
Console.WriteLine("Parrot is makevoice");
}
}
public class Penguin : IBird
{
public void MakeSound()
{
Console.WriteLine("Penguin is squawking");
}
}
public class Program
{
public static void Main()
{
IFlyingBird flyingBird = new Parrot();
flyingBird.Fly(); // This works correctly
flyingBird.MakeSound(); // This works correctly
IBird bird = new Penguin();
bird.MakeSound(); // This works correctly
// No Fly method to call, so no incorrect expectations
}
}
Output