Bỏ qua

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!