VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
OOP và thiết kếSOLID
Bài 10/12
5 phút

LSP — Class con phải thay được class cha

Nội dung bài · 6 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Sửa bằng interface
  4. 4.Thử ngay
  5. 5.Lỗi hay gặp
  6. 6.Tóm tắt

Vòng lặp hoàn tiền chạy ổn với mọi hình thức thanh toán, cho tới khi có thêm thẻ quà tặng. Class thẻ quà tặng kế thừa Payment nhưng Refund() lại ném lỗi, vì thẻ quà tặng không hoàn tiền được. Chương trình sập ở chỗ không ai ngờ, và LSP giúp tránh loại lỗi này.

Khái niệm

🦆 LSP (Liskov Substitution Principle): object của class con phải dùng thay được cho object của class cha mà chương trình vẫn chạy đúng.

Class con không được làm ít hơn những gì class cha đã hứa. Class cha có Refund() thì mọi class con đều phải hoàn tiền được.

Ví dụ

// SAI — GiftCard kế thừa nhưng không làm được Refund
var payments = new List<Payment>
{
    new CardPayment(),
    new GiftCardPayment(),
};

foreach (Payment p in payments)
{
    p.Refund();   // sập ở GiftCardPayment
}

abstract class Payment
{
    public abstract void Refund();
}

class CardPayment : Payment
{
    public override void Refund() =>
        Console.WriteLine("Hoàn về thẻ");
}

class GiftCardPayment : Payment
{
    public override void Refund()
    {
        throw new NotSupportedException(
            "Thẻ quà tặng không hoàn được");
    }
}
  • Vòng lặp tin rằng mọi Payment đều hoàn tiền được, vì class cha đã hứa như vậy.
  • GiftCardPayment phá lời hứa đó bằng NotSupportedException (exception có sẵn báo thao tác không được hỗ trợ), nên thay nó vào chỗ Payment là chương trình sập.
  • Lỗi không nằm ở vòng lặp, mà ở chỗ GiftCardPayment không nên có Refund().

Sửa bằng interface

Chỉ class nào hoàn tiền được mới implement IRefundable, như ở bài Interface hay abstract class:

var refunds = new List<IRefundable>
{
    new CardPayment(),
};

foreach (IRefundable r in refunds)
{
    r.Refund();
}

abstract class Payment
{
    public decimal Amount { get; set; }
}

interface IRefundable
{
    void Refund();
}

class CardPayment : Payment, IRefundable
{
    public void Refund() =>
        Console.WriteLine("Hoàn về thẻ");
}

// Không hứa hoàn tiền, nên không vào được list trên
class GiftCardPayment : Payment
{
}

Thử ngay

Chép khối SAI ở phần Ví dụ vào Program.cs, đổi chỗ hai phần tử trong list để new GiftCardPayment() đứng đầu, rồi chạy dotnet run.

Đoán trước khi chạy: dòng "Hoàn về thẻ" có được in ra không?

Xem kết quả
Unhandled exception. System.NotSupportedException: Thẻ quà tặng không hoàn được

Không. Vòng lặp sập ngay ở phần tử đầu, nên CardPayment chưa kịp hoàn tiền. Lỗi kiểu này chỉ lộ ra khi dữ liệu thật có thẻ quà tặng, nên rất khó phát hiện sớm.

Lỗi hay gặp

Override chỉ để ném exception. Đây là dấu hiệu rõ nhất của vi phạm LSP.

// SAI — nhận tại cửa hàng thì không có mã vận đơn
abstract class Shipment
{
    public abstract string Track();
}

class StorePickup : Shipment
{
    public override string Track()
    {
        throw new NotSupportedException();
    }
}

Nếu class con không làm được một việc của class cha, thì nó không phải là một loại của class cha. Hãy dùng interface nhỏ hơn hoặc composition.

// ĐÚNG — chỉ loại có mã vận đơn mới hứa Track
abstract class Shipment
{
    public string Address { get; set; } = "";
}

interface ITrackable
{
    string Track();
}

class HomeDelivery : Shipment, ITrackable
{
    public string Track() => "VN123456";
}

class StorePickup : Shipment
{
}

Tóm tắt

  • LSP: class con phải thay được class cha mà chương trình vẫn chạy đúng.
  • Class con không được làm ít hơn những gì class cha đã hứa.
  • Override chỉ để ném NotSupportedException là dấu hiệu vi phạm.
  • Cách sửa: tách khả năng ra interface riêng, chỉ class làm được mới implement.

Tự kiểm tra

0/3 câu
Câu 1

Class Bird có method Fly(). Class Penguin : Bird override Fly() bằng cách ném exception. Vấn đề là gì?

Câu 2

Cách sửa hợp lý cho ví dụ Bird và Penguin?

Câu 3

Dấu hiệu nào cho thấy có thể đang vi phạm LSP?

OCP — Mở rộng mà không sửa code cũISP — Interface nhỏ, đúng việc

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Sửa bằng interface
  4. 4.Thử ngay
  5. 5.Lỗi hay gặp
  6. 6.Tóm tắt