OCP — Mở rộng mà không sửa code cũ
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 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
90000Checkoutchỉ biếtIDiscount, 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.
Checkoutkhô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
80000Dò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/switchtheo 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âuMethod 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ì?
"Đóng với việc sửa đổi" trong OCP nghĩa là gì?
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?