What is Liskov Substitution Principle?
- The principle defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application.
- The principle of opening and closing determines that the instances of the subclass can replace the parent class instance while ensuring the correctness of the program.
Take real examples?
We need to sum all integers in an array. A little more “modern” we need to sum the even numbers in this membrane for example. Hmmm!. Let’s start the project!
The first is to initialize 2 classes. Class SumCalculator
- The Calculate () method to sum the numbers in the array. Class EvenNumbersSumCalculator inherits class SumCalculator
- The Calculate () method to sum even numbers in an array (note: The new keyword is used here to indicate that the programmer is creating a new version of this method inside the subclass).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class SumCalculator { protected readonly int[] _numbers; public SumCalculator(int[] numbers) { _numbers = numbers; } public int Calculate() => _numbers.Sum(); } public class EvenNumbersSumCalculator: SumCalculator { public EvenNumbersSumCalculator(int[] numbers) :base(numbers) { } public new int Calculate() => _numbers.Where(x => x % 2 == 0).Sum(); } |
- Now, let’s test these two functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Program { static void Main(string[] args) { var numbers = new int[] { 5, 7, 9, 8, 1, 6, 4 }; SumCalculator sum = new SumCalculator(numbers); Console.WriteLine($"The sum of all the numbers: {sum.Calculate()}"); Console.WriteLine(); EvenNumbersSumCalculator evenSum = new EvenNumbersSumCalculator(numbers); Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}"); } } |
- The results of the program will be:
1 2 3 4 | The sum of all the numbers: 40 The sum of all the even numbers: 18 |
- So this is fine then what =)). But we know that an object of a subtype can be assigned to a variable of its parent type , ie the base type can be used instead of the derived type (learn polymorphism). In this example we can store EvenNumbersSumCalculator as a SumCalculator variable.
1 2 3 | SumCalculator evenSum = new EvenNumbersSumCalculator(numbers); Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}"); |
- Now the original 2 classes will fix a bit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class SumCalculator { protected readonly int[] _numbers; public SumCalculator(int[] numbers) { _numbers = numbers; } public virtual int Calculate() => _numbers.Sum(); } public class EvenNumbersSumCalculator: SumCalculator { public EvenNumbersSumCalculator(int[] numbers) :base(numbers) { } public override int Calculate() => _numbers.Where(x => x % 2 == 0).Sum(); } |
- Test the function to calculate the even total
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Program { static void Main(string[] args) { var numbers = new int[] { 5, 7, 9, 8, 1, 6, 4 }; SumCalculator sum = new SumCalculator(numbers); Console.WriteLine($"The sum of all the numbers: {sum.Calculate()}"); Console.WriteLine(); SumCalculator evenSum = new EvenNumbersSumCalculator(numbers); Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}"); } } |
- The results of the program will be:
1 2 3 4 | The sum of all the numbers: 40 The sum of all the even numbers: 40 |
- As we can see, we don’t get the expected results because our evenSum variable is of type SumCalculator, which is a higher order class (base class). This means that the Sum method from SumCalculator will be executed. So this is obviously not true, because our subclass does not act as a surrogate class for the superclass (not true for Liskov Substitution Principle).
Now it is time to apply Liskov Substitution Principle
- At this time the original 2 classes will become.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // lớp cơ sở public abstract class Calculator { protected readonly int[] _numbers; public Calculator(int[] numbers) { _numbers = numbers; } public abstract int Calculate(); } // lớp con tính tổng public class SumCalculator : Calculator { public SumCalculator(int[] numbers) :base(numbers) { } public override int Calculate() => _numbers.Sum(); } // lớp con tính tổng số chẵn public class EvenNumbersSumCalculator: Calculator { public EvenNumbersSumCalculator(int[] numbers) :base(numbers) { } public override int Calculate() => _numbers.Where(x => x % 2 == 0).Sum(); } |
- Main function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Program { static void Main(string[] args) { var numbers = new int[] { 5, 7, 9, 8, 1, 6, 4 }; Calculator sum = new SumCalculator(numbers); Console.WriteLine($"The sum of all the numbers: {sum.Calculate()}"); Console.WriteLine(); Calculator evenSum = new EvenNumbersSumCalculator(numbers); Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}"); } } |
- Program results
- The results of the program will be:
1 2 3 4 | The sum of all the numbers: 40 The sum of all the even numbers: 18 |
- Whoa. We will have the same result ** 40 & 18. But it is important that we can store any subclass reference into a base class variable and the behavior will not change, which is the goal of Liskov Substitution Principle