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 9/12
5 phút

OCP — Mở rộng mà không sửa code cũ

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

Cửa hàng có giảm giá cho khách thường và khách thân thiết. Mỗi lần thêm loại khách mới, bạn lại mở method tính giảm giá ra thêm một nhánh if, và mỗi lần sửa code đang chạy là một lần có thể làm hỏng. OCP chỉ cách thêm tính năng mà không đụng vào code cũ.

Khái niệm

🔓 OCP (Open/Closed Principle): class nên mở cho việc mở rộng nhưng đóng với việc sửa đổi.

Nghĩa là thêm tính năng bằng cách viết code mới thay vì sửa code đang chạy. Cách làm thường gặp: đưa phần hay thay đổi ra sau một interface. Mỗi biến thể mới là một class mới implement interface đó.

Ví dụ

var checkout = new Checkout();
Console.WriteLine(
    checkout.Pay(100000m, new RegularDiscount()));
Console.WriteLine(
    checkout.Pay(100000m, new LoyalDiscount()));

interface IDiscount
{
    decimal Apply(decimal amount);
}

class RegularDiscount : IDiscount
{
    public decimal Apply(decimal amount) => amount;
}

class LoyalDiscount : IDiscount
{
    public decimal Apply(decimal amount) =>
        amount - amount * 10 / 100;
}

class Checkout
{
    public decimal Pay(
        decimal amount, IDiscount discount) =>
        discount.Apply(amount);
}
100000
90000
  • Checkout chỉ biết IDiscount, không biết có bao nhiêu loại giảm giá.
  • Thêm loại giảm giá mới là thêm class mới. Checkout không phải sửa.
  • Đây chính là đa hình và interface đã học ở các chương trước.

Thử ngay

Chép ví dụ trên vào Program.cs. Thêm class dưới đây vào cuối file, rồi in thêm checkout.Pay(100000m, new VipDiscount()) ngay sau hai lệnh in ở đầu file:

class VipDiscount : IDiscount
{
    public decimal Apply(decimal amount) =>
        amount - amount * 20 / 100;
}

Đoán trước khi chạy: dòng thứ ba in ra số nào?

Xem kết quả
100000
90000
80000

Dòng thứ ba là 80000, tức 100000 giảm 20%. Checkout không sửa dòng nào, tính năng mới nằm gọn trong class VipDiscount.

Lỗi hay gặp

Chuỗi if phình ra theo thời gian. Mỗi loại khách mới là một lần sửa method cũ.

// SAI — thêm loại khách là phải sửa method này
decimal Pay(decimal amount, string type)
{
    if (type == "regular")
    {
        return amount;
    }
    else if (type == "loyal")
    {
        return amount - amount * 10 / 100;
    }
    return amount;
}
// ĐÚNG — loại khách mới là một class IDiscount mới
decimal Pay(decimal amount, IDiscount discount)
{
    return discount.Apply(amount);
}

Làm trước cho mọi khả năng. Mới có một cách tính đã tạo interface để dành cho "sau này" thì chỉ tốn thêm file. Hãy áp dụng OCP khi thấy phần đó thật sự hay thay đổi.

Tóm tắt

  • OCP: thêm tính năng bằng code mới, không sửa code đang chạy.
  • Đưa phần hay thay đổi ra sau interface, mỗi biến thể là một class.
  • Chuỗi if/switch theo loại, phình ra mỗi lần có loại mới, là dấu hiệu nên dùng OCP.
  • Chỉ áp dụng cho phần thật sự hay thay đổi.

Tự kiểm tra

0/3 câu
Câu 1

Method ExportReport(string format) có switch cho "pdf", "excel", "csv". Tháng nào cũng thêm định dạng mới. Theo OCP nên làm gì?

Câu 2

"Đóng với việc sửa đổi" trong OCP nghĩa là gì?

Câu 3

Cửa hàng chỉ có đúng một cách tính phí ship, và chưa có kế hoạch đổi. Có nên tạo interface IShipping ngay không?

SRP — Một class, một trách nhiệmLSP — Class con phải thay được class cha

Nội dung bài

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