Open/Closed Principle (OCP)¶
Open/Closed Principle là nguyên lý thứ hai trong bộ nguyên lý SOLID, quy định rằng:
- Open for extension (Mở để mở rộng): Có thể thêm tính năng mới
- Closed for modification (Đóng để sửa đổi): Không cần thay đổi code hiện tại
Tại sao quan trọng?¶
- Giảm rủi ro: Không làm hỏng code đã hoạt động tốt
- Dễ bảo trì: Thêm tính năng mà không ảnh hưởng hệ thống cũ
- Tăng khả năng mở rộng: Hệ thống linh hoạt với yêu cầu mới
Ví dụ vi phạm OCP¶
Đây là cách implement SAI - vi phạm nguyên lý OCP:
class DiscountCalculator {
public double calculateDiscount(String customerType, double amount) {
if (customerType.equals("Regular")) {
return amount * 0.05; // 5% discount
} else if (customerType.equals("VIP")) {
return amount * 0.10; // 10% discount
}
// Nếu thêm loại khách hàng mới, phải SỬA code này!
return 0;
}
}
Vấn đề của cách làm trên:¶
- Phải sửa code cũ mỗi khi thêm loại khách hàng mới
- Vi phạm OCP: Code không "đóng để sửa đổi"
- Rủi ro cao: Có thể làm hỏng logic discount hiện tại
- Khó test: Phải test lại toàn bộ sau mỗi lần thay đổi
- Khó maintain: Code sẽ ngày càng phình to và phức tạp
Giải pháp tuân thủ OCP¶
Sử dụng Strategy Pattern để tuân thủ nguyên lý OCP:
Bước 1: Định nghĩa interface¶
// Interface định nghĩa hành vi chung
interface DiscountStrategy {
double calculateDiscount(double amount);
}
Bước 2: Implement cho từng loại khách hàng¶
// Khách hàng thường
class RegularCustomerDiscount implements DiscountStrategy {
public double calculateDiscount(double amount) {
return amount * 0.05; // 5% discount
}
}
// Khách hàng VIP
class VIPCustomerDiscount implements DiscountStrategy {
public double calculateDiscount(double amount) {
return amount * 0.10; // 10% discount
}
}
// Thêm loại mới mà KHÔNG cần sửa code cũ
class PremiumCustomerDiscount implements DiscountStrategy {
public double calculateDiscount(double amount) {
return amount * 0.15; // 15% discount
}
}
Bước 3: Context class sử dụng strategy¶
class DiscountCalculator {
private DiscountStrategy strategy;
public DiscountCalculator(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double calculateDiscount(double amount) {
return strategy.calculateDiscount(amount);
}
// Có thể thay đổi strategy runtime
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
}
Cách sử dụng:¶
public class Main {
public static void main(String[] args) {
double amount = 1000;
// Khách hàng thường
DiscountCalculator calculator = new DiscountCalculator(new RegularCustomerDiscount());
System.out.println("Regular: " + calculator.calculateDiscount(amount)); // 50
// Khách hàng VIP
calculator.setStrategy(new VIPCustomerDiscount());
System.out.println("VIP: " + calculator.calculateDiscount(amount)); // 100
// Khách hàng Premium (thêm mới)
calculator.setStrategy(new PremiumCustomerDiscount());
System.out.println("Premium: " + calculator.calculateDiscount(amount)); // 150
}
}
Lợi ích của giải pháp tuân thủ OCP¶
Mở rộng dễ dàng¶
- Thêm loại khách hàng mới chỉ cần tạo class implement
DiscountStrategy - Không cần sửa bất kỳ code nào đã viết
An toàn¶
- Code cũ không bị động chạm → giảm rủi ro bug
- Mỗi strategy độc lập → lỗi ở strategy này không ảnh hưởng strategy khác
Dễ test¶
- Test từng strategy riêng biệt
- Test logic mới không cần retest logic cũ
Linh hoạt¶
- Có thể thay đổi strategy trong runtime
- Dễ dàng kết hợp nhiều strategy khác nhau
Tuân thủ SOLID¶
- Single Responsibility: Mỗi strategy chỉ lo một việc
- Open/Closed: Đạt được mục tiêu chính của OCP
- Dependency Inversion: Phụ thuộc vào abstraction (interface)
Ví dụ mở rộng thêm¶
Giả sử sau này cần thêm nhiều loại discount phức tạp:
// Discount theo ngày trong tuần
class WeekendDiscount implements DiscountStrategy {
public double calculateDiscount(double amount) {
LocalDate today = LocalDate.now();
if (today.getDayOfWeek() == DayOfWeek.SATURDAY ||
today.getDayOfWeek() == DayOfWeek.SUNDAY) {
return amount * 0.20; // 20% discount vào cuối tuần
}
return 0;
}
}
// Discount theo số lượng mua
class BulkPurchaseDiscount implements DiscountStrategy {
private int quantity;
public BulkPurchaseDiscount(int quantity) {
this.quantity = quantity;
}
public double calculateDiscount(double amount) {
if (quantity >= 100) return amount * 0.25;
if (quantity >= 50) return amount * 0.15;
if (quantity >= 10) return amount * 0.08;
return 0;
}
}
// Kết hợp nhiều discount
class CombinedDiscount implements DiscountStrategy {
private List<DiscountStrategy> strategies;
public CombinedDiscount(List<DiscountStrategy> strategies) {
this.strategies = strategies;
}
public double calculateDiscount(double amount) {
return strategies.stream()
.mapToDouble(strategy -> strategy.calculateDiscount(amount))
.max() // Lấy discount cao nhất
.orElse(0);
}
}
Quan trọng: Tất cả những extension này được thêm mà không cần sửa một dòng code nào trong DiscountCalculator hay các strategy đã có!
Kết luận¶
Open/Closed Principle giúp tạo ra code:
- Ổn định: Code cũ không bị thay đổi
- Linh hoạt: Dễ dàng thêm tính năng mới
- Bảo trì: Giảm thiểu rủi ro khi phát triển
- Kiểm thử: Test dễ dàng và tin cậy
Nhớ rằng: "Mở để mở rộng, đóng để sửa đổi" - đây là chìa khóa để xây dựng phần mềm bền vững!