Hiểu Liskov Substitution Principle qua ví dụ !

Liskov Substitution Principle là gì ?

  • The principle defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application.
  • Nguyên tắc đóng mở xác đinh rằng các instance của lớp con có thể thay thế được instance lớp cha mà vẫn đảm bảo tính đúng đắn của chương trình.

Lấy ví dụ thực tế ?

Chúng ta cần tính tổng các số nguyên trong một mảng. "Hiện đại" hơn một chút chúng ta cần tính tổng của các số chẵn trong màng này chẳng hạn 😃. Hmmm !. Khởi tạo project thôi !

Trước hết là khởi tạo 2 class. Class SumCalculator

  • Method Calculate() để tính tổng các số trong mảng. Class EvenNumbersSumCalculator kế thừa class SumCalculator
  • Method Calculate() để tính tổng các số chẵn trong mảng (note : Từ khóa new được sử dụng ở đây để chỉ ra rằng người lập trình đang tạo ra một phiên bản mới cho phương thức này bên trong lớp con).
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();
}
  • Bây giờ thì test 2 hàm này thôi :
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()}");
    }
}
  • Kết quả của chương trình sẽ là :
The sum of all the numbers: 40

The sum of all the even numbers: 18
  • Ơ thế này là ổn rồi còn gì =)). Nhưng chúng ta biết rằng một object thuộc kiểu con có thể gán cho biến thuộc kiểu cha, tức là kiểu cơ sở có thể dùng để thay thế cho kiểu dẫn xuất (tìm hiểu tính đa hình). Trong ví dụ này thì chúng ta có thể lưu EvenNumbersSumCalculator dưới dạng biến SumCalculator
SumCalculator evenSum = new EvenNumbersSumCalculator(numbers);
Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}");
  • Bây giờ 2 class ban đầu sẽ sửa một chút
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 thử lại hàm tính tổng số chẵn xem sao 😃
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()}");
    }
}
  • Kết quả của chương trình sẽ là :
The sum of all the numbers: 40

The sum of all the even numbers: 40
  • Như chúng ta có thể thấy, chúng ta không nhận được kết quả như mong đợi vì biến evenSum của chúng ta thuộc loại SumCalculator, là một lớp bậc cao hơn (lớp cơ sở). Điều này có nghĩa là phương thức Sum từ SumCalculator sẽ được thực thi. Vì vậy, điều này rõ ràng là không đúng, bởi vì lớp con của chúng ta không hoạt động như một lớp thay thế cho lớp cha(không đúng với Liskov Substitution Principle) .

Bây giờ là lúc áp dụng Liskov Substitution Principle

  • Lúc này 2 class ban đầu sẽ trở thành.
// 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();
}
  • Hàm main
 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()}");
    }
}
  • Kết quả chương trình
  • Kết quả của chương trình sẽ là :
The sum of all the numbers: 40

The sum of all the even numbers: 18
  • Uầyyy. Chúng ta sẽ có kết quả tương tự** 40 & 18. Nhưng quan trọng là chúng ta có thể lưu trữ bất kỳ tham chiếu lớp con nào vào một biến lớp cơ sở và hành vi sẽ không thay đổi, đó là mục tiêu của Liskov Substitution Principle 😉

All Rights Reserved