Liskov Substitution Principle in C#: Cash In on Subclassing
Liskov Substitution Principle in C#: Cash In on Subclassing

Liskov Substitution Principle in C#: Cash In on Subclassing

In this post, we’re going to talk about the Liskov Substitution Principle, a programming principle that is supposed to help you design better, more robust, and more flexible code, especially if your code has an inheritance.

Liskov Substitution Principle in C# states that all classes should be substitutable for their base C# classes. It should be possible to replace an object of type S with an object of a subtype T and still maintain the same behavior before and after the substitution.

This was a quick overview of the principle, but if you want to see some code examples, then keep reading!

What does Liskov Substitution Principle specify?

The concept of inheritance in object-oriented programming is compelling and has been used for a very long time. The two main reasons inheritance is a useful concept are code reuse and the ability to extend the functionality of a class. However, this power comes with a tradeoff in that it introduces a dependency on the parent class. This dependency can lead to poor program design and increases the complexity of a program.

One way to have correct implementation while dealing with inheritance is by using the Liskov Substitution Principle.

Barbara Liskov first introduced the Liskov Substitution Principle (LSP) in 1987. It says that in a hierarchy, if S, a subtype of T, is used in place of type T, then type S must be usable in every place where T is specified and behave in the same way.

In other words, you should be able to use any derived class in place of the base one, and the behavior should be unaffected.

LSP is considered one of the SOLID design principles of Object-oriented programming, and it is based on inheritance.

Liskov Substitution Principle code example

Let’s talk about code where the Liskov Substitution Principle could be broken. Let say that we have a Rectangle class that has properties Width, Height, and a method GetArea that returns calculated rectangle area.

class Program
{
    static void Main(string[] args)
    {
        Rectangle rectangle = new Rectangle
        {
            Width = 7,
            Height = 6
        };
        Console.WriteLine(rectangle.GetArea()); //output is 42

        rectangle = new Square
        {
            Width = 7,
            Height = 6
        };
        Console.WriteLine(rectangle.GetArea()); //output is 49
    }
}

public class Rectangle
{
    public int Width { get; set; }
    public int Height { get; set; }

    public virtual int GetArea()
    {
        return Width * Height;
    }
}

public class Square: Rectangle
{
    public override int GetArea()
    {
        return Width * Width;
    }
}

If we define another, Square class, that inherits from the Rectangle, but the calculation in the GetArea is different, once we try to replace the instance of the Rectangle class for the Square class, the output result would be different. And thus, the code breaks the LSP.

How to solve this situation using the Liskov Substitution principle? The simplest way would be to define a Shape abstract class, that has Width and Height properties. And both Rectangle and Shape class inherit from it. This way the correct hierarchy would be in place.

LSP Rules

When you’re trying to understand the Liskov Substitution Principle, it can be hard to find a concise explanation of the rules. The rules are simple enough to state, but the implications can be tricky to wrap your head around. The impact of a violation of the Liskov Substitution Principle can be significant, and it can lead to a lot of time spent fixing bugs. That’s why Liskov Substitution Principle is important if your existing code contains a lot of supertype-subtype relationships.

The LSP rules are:

  1. Subclass should implement all classes of the base class. This means there should be no methods that throw NotImplementedException. This was explained in more detail in the post about the Interface Segregation design principle.
  2. Overridden method of the parent class should accept the same parameters in the child class. For example, you have a method CalculateSquare in the parent class that takes an int as a parameter. This means you can’t specify the same method in the child class, override it with the keyword new, and create an additional rule that will check that the number is not negative. If you try to pass -3 in the client code but use the child class instead of the superclass, the client code will get the exception. And that’s not the behavior that was intended by the base class.
class MathOperation
{
    internal virtual long CalculateSquare(int number)
    {
        return number * number;
    }
}

class PositiveMathOperation: MathOperation
{
    internal override long CalculateSquare(int number)
    {
        if (number < 0)
        {
            throw new Exception("Negative value");
        }
        return number * number;
    }
}

What are SOLID principles?

The LSP is a part of the SOLID principles. The SOLID principles are a group of principles for Object-Oriented code. They were developed by Robert C. Martin, more commonly known as Uncle Bob.

SOLID principles are a great way to make sure you’re writing good code in any language. The principles are as follows:

  • Single Responsibility Principle – The Single Responsibility Principle states that every class should have responsibility for a single part of the functionality provided by the software and that the class should entirely encapsulate responsibility.
  • Open/Closed Principle – The Open/Closed Principle is based on the idea that as objects evolve, they should be able to add functionality without modifying their existing code. The idea is that the system is open for extension and closed for modification.
  • Liskov Substitution Principle – This article covers the LSP.
  • Interface Segregation Principle – The Interface Segregation Principle states that clients should not be forced to depend upon methods they don’t use. It is a principle used to achieve two main goals: reduce dependencies among classes and reduce the complexity of individual classes.
  • Dependency inversion Principle – The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

Conclusion

The Liskov Substitution Principle is a popular and important concept in software design. Especially when you have an inheritance in your project. The Liskov Substitution Principle is a very simple concept to understand, and it will help you design better software, so it’s worth learning.

However, inheritance has become a less popular way to share functionality between classes in modern applications. Instead, you should use dependency injection to share functionality. That’s why Liskov Substitution Principle is less popular nowadays. Nevertheless, it’s a concept worth knowing while designing the apps.