VieTopik
Tiếng HànTiếng Anh.NET (C#)Java
  • 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ế (Java)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ụ

// Main.java
void main() {
    var checkout = new Checkout();
    System.out.println(
        checkout.pay(100000, new RegularDiscount()));
    System.out.println(
        checkout.pay(100000, new LoyalDiscount()));
}

// Discount.java
interface Discount {
    long apply(long amount);
}

// RegularDiscount.java
class RegularDiscount implements Discount {
    @Override
    public long apply(long amount) {
        return amount;
    }
}

// LoyalDiscount.java
class LoyalDiscount implements Discount {
    @Override
    public long apply(long amount) {
        return amount - amount * 10 / 100;
    }
}

// Checkout.java
class Checkout {
    public long pay(long amount, Discount discount) {
        return discount.apply(amount);
    }
}
100000
90000
  • Checkout chỉ biết Discount, 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

Giữ các file của ví dụ. Tạo thêm file dưới đây, rồi thêm System.out.println(checkout.pay(100000, new VipDiscount())); ngay sau hai lệnh in trong main:

// VipDiscount.java
class VipDiscount implements Discount {
    @Override
    public long apply(long amount) {
        return 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
// Checkout.java
class Checkout {
    public long pay(long amount, String type) {
        if (type.equals("regular")) {
            return amount;
        } else if (type.equals("loyal")) {
            return amount - amount * 10 / 100;
        }
        return amount;
    }
}
// ĐÚNG — loại khách mới là một class Discount mới
// Checkout.java
class Checkout {
    public long pay(long amount, Discount discount) {
        return discount.apply(amount);
    }
}

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 Shipping 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