SOLID Design Principles: The Liskov Substitution

Avinash Dhumal
4 min readOct 3, 2022

Introduction

In today’s article, I am going to talk about the Liskov substitution principle, and how to implement it with a real time example.

Before moving on to the Liskov substitution principle, I encourage you to read my previous articles on Single Responsibility and Open Closed principles.

What is Liskov Substitution?

Liskov substitute is nothing but an extended version of Open Closed principles. The Liskov Substitution principle states that objects of a super class should be replaceable with objects of its subclasses without breaking the application’s behavior.

In order to achieve above criteria, we have to ensure to follow some of the rules mentioned below:

  • Method of a subclass should have the same input parameters that are defined for the method of a superclass.
  • Method of a subclass should have the same return type that is defined for the method of a superclass.
  • No new exceptions to be thrown by your subclass methods.

When it comes to actual development, it becomes difficult to enforce rules to apply Liskov Substitutes as it becomes more important to maintain the behavior of your application instead of a structure. Unfortunately, there is no direct method or way to enforce rules to have Liskov Substitution principle implemented. As most of the compilers take care of any violations with respect to structure, it can’t enforce or validate behavior of the code.

The only way to ensure your code follows Liskov Substitute is to have a manual checklist which will be enforced during the code reviews. You can also have test cases written for your super classes and subclasses to check if the behavior of the application changes and ensure to execute them every time whenever the changes happen to your application.

Now, let’s take an example to implement with Liskov Substitution.

Example

public class ReadFile
{
public virtual void Read()
{
Console.WriteLine("Reading from TEXT File...");
//default behavior
}
}

We have defined a Super Class called ReadFile which has a virtual method called Read() which does the default operation to read from a text file.

public class ReadXMLFile : ReadFile
{
public override void Read()
{
Console.WriteLine("Reading from XML File...");
//extended behavior
}
}
public class ReadFlatFile : ReadFile
{
public override void Read()
{
Console.WriteLine("Reading from Flat File...");
//extended behavior
}
}

Here we have inherited ReadFile Super Class with 2 derived classes ReadXMLFileand ReadFlatFile which are overriding the Read() method and extending logic to read the file from XML and Flat files respectively.

Now let’s understand how the Liskov Substitution works here. As it says, my Super Class objects should be replaceable with derived ones. So let’s take a look at the following code block to understand more.

static void Main(string[] args)
{
string _fileType = "flat";
ReadFile _file;
switch (_fileType)
{
case "txt":
_file = new ReadFile();
// calling default behavior
break;
case "xml":
_file = new ReadXMLFile();
//Super class object substituted with derived class
break;
case "flat":
_file = new ReadFlatFile();
//Super class object substituted with derived class
break;
default:
_file = new ReadFile();
break;
}
_file.Read();
}

If you take a look at the code block, we have defined an object of Super Class ReadFile called _file and depending upon input parameters to _fileType variable i.e. text, xml or flat, we are initiating derived class objects. So, if we pass the input values one by one and run the program, we will get the following output.

The logic gives us the full control to point a super class object to any derived class and execute the functions without breaking the behavior of the classes which is nothing but Liskov Substitution principle.

Conclusion

With the above example, we have now understood how the open closed principle got extended and implemented the Liskov Substitution principle without breaking the behavior of the classes.

Here is the complete code for your reference.

using System;namespace Liskov_Substitution
{
internal class Program
{
public class ReadFile
{
public virtual void Read()
{
Console.WriteLine("Reading from TEXT File...");
//default behavior
}
}
public class ReadXMLFile : ReadFile
{
public override void Read()
{
Console.WriteLine("Reading from XML File...");
//extended behavior
}
}
public class ReadFlatFile : ReadFile
{
public override void Read()
{
Console.WriteLine("Reading from Flat File...");
//extended behavior
}
}
static void Main(string[] args)
{
string _fileType = "flat";
ReadFile _file;
switch (_fileType)
{
case "txt":
_file = new ReadFile();
// calling default behavior
break;
case "xml":
_file = new ReadXMLFile();
//Super class object substituted with derived class
break;
case "flat":
_file = new ReadFlatFile();
//Super class object substituted with derived class
break;
default:
_file = new ReadFile();
break;
}
_file.Read();
}
}
}

If you enjoy the content I create and would like to show your appreciation, you can buy me a coffee!

--

--

Avinash Dhumal

13+ years of software architect, design, development, management, and support experience in Microsoft technologies using Azure & AWS Cloud services.