Today we will be going over the L of the SOLID principles, and this stands for Liskov (Substitute), this principle states that you should be able to use any derived class instead of a parent class and have it behave in the same manner without modification. This principle ensures that a derived class won’t affect the behaviour of a parent class. So a derived class must be substitutable for the base class.
A simple read world example is that a mother may be a nurse, however, the daughter wants to be a software developer. So although they are in the same family hierarchy the daughter cannot replace the mother.
C# example of code without Liskov Principle
Create a console application using your chosen IDE and at the root of the project create a base class called Calculator.cs and include in it a protected readonly array of integers called _numberList and since the array is of type protected readonly then the value can only be set on initialization or in a constructor.
In addition, we will create a method within this called Calculate that just returns the sum of the numbers in the list
public class Calculator
{
protected readonly int[] _numberList;
public Calculator(int[] numberList)
{
_numberList = numberList;
}
public int Calculate()
{
return _numberList.Sum();
}
}
Furthermore, we want to be able to sum together even numbers so we will now create a class that inherits from the base class and implements its own Calculate method.
public class EvenNumCalculator : Calculator
{
public EvenNumCalculator(int[] _numberList) : base(_numberList)
{
}
public new int Calculate()
{
return _numberList.Where(x => x % 2 == 0).Sum();
}
}
So as you can see from the above EvenNumCalculator is inheriting from the Calculator class and overrides the Calculate method to return the sum of integers within the _numberList that are even by using LINQ syntax.
The base keyword is used to access members that are found within the base class and access those members from within the derived class, so in the example above : base(_numberList) calls the constructor of the base with the _numberList as a parameter, this ensures that the _numberList property is initialized before the initialization of the EvenNumCalculator begins.
So if we open our Program.cs file and create an array of integers as well as instances of the classes we have made and passing the array into the constructor of the instances.
var intArray = new int[]{1,2,5,4,7,9,11,22,27};
Calculator calculator = new Calculator(intArray);
Console.WriteLine($"The sum of the calculation is: {calculator.Calculate()}");
Console.WriteLine();
EvenNumCalculator evenNumCalculator = new EvenNumCalculator(intArray);
Console.WriteLine($"The sum of the even numbers: {evenNumCalculator.Calculate()}");
The result I get is below.
Adding a solution
We will alter the code to get to a solution, so when a child class inherits from a parent class then the child class is technically a parent class and so if this is true then we should be able to store a new reference to an EvenNumCalculator as a Calculator variable and none of the functionality should be different, so lets try this!
Change the variable of EvenNumCalculator to be Calculator so it’s like the below.
Calculator evenNumCalculator = new EvenNumCalculator(intArray);
If you run the code again you will see that the output is 88 for both.
This is not the expected result because the variable of type Calculator which is the base class meaning that the “Calculate” method from Calculator will be ran. This is not right as the child class is not acting like a substitute for the base class.
To fix this issue we could just change the method in Calculator to be virtual and in the EvenNumCalculator class change the method to be override.
public virtual int Calculate()
{
return _numberList.Sum();
}
public override int Calculate()
{
return _numberList.Where(x => x % 2 == 0).Sum();
}
The above code changes work because of the virtual and override as we have a child object reference stored in a parent object variable and in the child class the method is set as override then the method in the child class is used instead. However, the derived class behaviour has changed and cannot replace the base class.
Implement LSP Principle
So we need to alter the Calculator class to become abstract and change the Calculate method to be abstract and remove the body of the method. We make the class abstract so we can’t make instances of the said class and so multiple derived classes can share a common definition of the base class. This can be said of abstract methods too, only derived classes can implement abstract methods, abstract methods in abstract classes only have the definition and not the body of the method.
public abstract class Calculator
{
protected readonly int[] _numberList;
public Calculator(int[] numberList)
{
_numberList = numberList;
}
public abstract int Calculate();
}
Furthermore, we will now need to create an additional class that inherits from the base class Calculator and overrides the abstract method to implement the sum of the _numberList variable.
public class CalculateSum : Calculator
{
public CalculateSum(int[] _numberList) : base(_numberList)
{
}
public override int Calculate()
{
return _numberList.Sum();
}
}
So we now have 3 classes Calculator, CalculateSum and EvenNumCalculator. So now open the Program.cs file and ensure the code is like the below.
var intArray = new int[]{1,2,5,4,7,9,11,22,27};
Calculator calculator = new CalculateSum(intArray);
Console.WriteLine($"The sum of the calculation is: {calculator.Calculate()}");
Console.WriteLine();
Calculator evenNumCalculator = new EvenNumCalculator(intArray);
Console.WriteLine($"The sum of the even numbers: {evenNumCalculator.Calculate()}");
So we can see that we have stored a subclass reference into a base class variable, if you run the code you should the results as 88 and 28 which is correct behaviour.
One of the main benefits of implementing LSP in your code is that the classes are loosely coupled and the ability to have classes that can be used interchangeably without modifying the code, making it easier to reuse the code when developing new features.
This concludes my article and I hope you enjoyed it, don’t forget to clap, follow and share.
References