# The Liskov Substitution Principle (LSP)

> Subtypes must be substitutable for their base types.

Đơn gì thì nếu code của bạn đang hoạt động đúng với object `<span class="editor-theme-code">Base</span>` thì việc truyền vào một object `<span class="editor-theme-code">Derived : Base</span>` không được làm đoạn code đó gặp vấn đề.

### Ví dụ kinh điển Square và Rectangle

Về mặt toán học chúng ta đều biêt hình vuông cũng là một hình chữ nhật. Thế nên rất dễ dẫn tới thiết kế như sau:

```c#
public class Rectangle
{
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }

    public int Area()
    {
        return Width * Height;
    }
}
public class Square : Rectangle
{
    public override int Width
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
    }

    public override int Height
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
    }
}
```

Nhìn qua thì thiết kế này vẫn đảm bảo `<span class="editor-theme-code">Width == Height</span>`. Nhìn riêng thì có vẻ như 2 class này hoàn toàn hợp lý. Nhưng hãy xem cách sử dụng dưới đây:

```c#
public int CalculateArea(Rectangle rectangle) 
{ 
  rectangle.Width = 5; 
  rectangle.Height = 4; 

  return rectangle.Area();
}

public void Main()
{
  Console.WriteLine(CalculateArea(new Rectangle())); // 20
  Console.WriteLine(CalculateArea(new Square())); // 16
}
```

Như ví dụ trên chúng ta có thể thấy kết quả khác nhau và không đúng với `<span class="editor-theme-code">Square</span>`. Hay nói cách khác `<span class="editor-theme-code">Square</span>` không thể thay thế cho `<span class="editor-theme-code">Rectangle</span>`.

Điểm đáng chú ý là nếu chỉ xét riêng `<span class="editor-theme-code">Square</span>` thì nó vẫn là một hình vuông hợp lệ với `<span class="editor-theme-code">Width == Height</span>`.

Vấn đề nằm ở **contract** của `<span class="editor-theme-code">Rectangle</span>`. Client sử dụng `<span class="editor-theme-code">Rectangle</span>` có quyền kỳ vọng rằng `<span class="editor-theme-code">rectangle.Width = 5;</span>` chỉ thay đổi `<span class="editor-theme-code">Width</span>` chứ không âm thầm thay đổi cả `<span class="editor-theme-code">Height</span>`. `<span class="editor-theme-code">Square</span>` phá vỡ kỳ vọng đó.

Do đó trong thiết kế hướng đối tượng. quan hệ "Square **IS-A** Rectangle" không nên chỉ được quyết định dựa trên quan hệ ngoài đời thực.

> LSP thực sự quan tâm tới **IS-A về behavior** về mặt behavior. Nếu behavior của subtype không tương thích với những kỳ vọng hợp lý của client với base type thì tức là inheritance đó không phù hợp.

### Contract quan trọng hơn inheritance

Có thể xem mỗi public API là một contract giữa object và client.

Contract thường gồm:

- **Precondition:** điều kiện client phải đáp ứng trước khi gọi.
- **Postcondition:** điều object đảm bảo sau khi thực hiện.

Nói đơn giản **Derived class không được đòi hỏi nhiều hơn và không được cung cấp ít hơn base class.**

Một ví dụ cho việc subtype đòi hỏi precondition mạnh hơn base type:

```c#
public class Account
{
  public virtual void Withdraw(int amount)
  {
    if (balance < amount) return;

    // Withdraw
  }
}

public class ChildAccount : Account
{
  public override void Withdraw(int amount)
  {
    if (balance < amount || amount > 100) return;
    
    // Withdraw
  }
}
```

Trong ví dụ này code vốn chạy đúng với `<span class="editor-theme-code">Account</span>` nhưng có thể thất bại với `<span class="editor-theme-code">ChildAccount</span>`.

Một ví dụ khác, chúng ta có `<span class="editor-theme-code">Bird.Fly()</span>` client có quền kỳ vọng object đó có thể bay nhưng `<span class="editor-theme-code">Penguin : Bird</span>` và `<span class="editor-theme-code">Penguin.Fly()</span>` lại không thực hiện được kỳ vọng đó thì tức là hierarchy đang có vấn đề.

### LSP liên quan trực tiếp tới OCP

Một dấu hiệu rất phổ biến khi vi phạm LSP là client bắt đầu phải kiểm tra subtype

```c#
public void Process(Rectangle rectangle) 
{ 
  if (rectangle is Square) 
  { 
    // Special case for Square 
  } 
  else 
  { 
    // Normal Rectangle behavior 
  } 
}
```

Ban đầu abstraction này được tạo ra để client chỉ cần biết `<span class="editor-theme-code">Rectangle</span>` nhưng giờ client cần phải đọc lại để biết thêm về `<span class="editor-theme-code">Square</span>`. Với mỗi subtype lại cần thêm một nhánh để xử lý riêng. Khi đó abstraction gần như không còn ý nghĩa.

Đây cũng là lý do mà LSP có quan hệ chặt chẽ với OCP. Khi subtype không thể thay thế base type, client thường phải thêm type checking để xử lý ngoại lệ. Mỗi subtype mới buộc client phải sửa và khiến code không còn **closed for modification**.

### Tổng kết

LSP không đơn giản là quy tắc về inheritance. Nó là quy tắc về đảm bảo **abstraction thực sự đáng tin cậy**.

Câu hỏi hữu ích khi đánh giá một quan hệ inheritance là: "**Nếu thay Base bằng Derived, những đoạn code đang sử dụng Base có còn hoạt động đúng theo kỳ vọng ban đầu không?**"

Nếu câu trả lời là không thì chưa chắc subtype đã đã thỏa mãn **IS-A về mặt behavior** và đó là điều mà LSP muốn chúng ta tránh.