Menü

Neominal

BT Hizmetleri ve Danışmanlığı

5 Mayıs 20264 dk okuma16 görüntüleme
Part 2/5: Open/Closed Principle

Şöyle bir senaryo düşünelim. Sisteminizde transfer ücretleri var ve her şey güzel çalışıyor.

Bir gün iş birimi geliyor: "Premium müşteriler için indirimli ücret ekleyelim" diyor.
Tamam, makul bir istek. Kodu açıyorsunuz, if-else ekliyorsunuz, deploy ediyorsunuz.

Bir ay sonra aynı kişi geliyor: "Kripto transferler için sabit ücret olsun" diyor. Tekrar if-else.

Üç ay sonra: "Kurumsal müşteriler için ayrı bir model lazım" diyor. Yine if-else.

Bir yıl sonra o metodun içine bakmaktan korkar hale geliyorsunuz.

Nedir bu prensip aslında?

Open/Closed Principle'ın söylediği şey şu: yazılım genişlemeye açık, değişime kapalı olmalı.

"Değişime kapalı" kulağa garip geliyor, değil mi? Kod değişmeyecek mi hiç? Hayır, kasıt şu: mevcut, çalışan koda dokunmadan yeni davranış ekleyebilmeliyiz. Yeni bir şey eklemek için eski bir şeyi bozmak zorunda kalmamalıyız.

Bunu sağlamanın en temiz yolu: soyutlama ve strateji deseni.

❌ Önce yanlışı görelim

// Çoğu projede ücret hesaplama şöyle başlar:

class FeeCalculator {
    BigDecimal calculate(String customerType, BigDecimal amount) {
        if (customerType.equals("STANDARD")) {
            return amount.multiply(new BigDecimal("0.001")); // %0.1
        } else if (customerType.equals("PREMIUM")) {
            return amount.multiply(new BigDecimal("0.0005")); // %0.05
        } else if (customerType.equals("CRYPTO")) {
            return new BigDecimal("2.50"); // sabit ücret
        }
        // Yeni tip gelince buraya bir else if daha...
        throw new IllegalArgumentException("Bilinmeyen tip: " + customerType);
    }
}


Bu kod her yeni ücret modeli geldiğinde değişmek zorunda. Peki bu ne demek?

  • Her değişiklikte mevcut if-else zincirine dokunuyorsunuz.

  • STANDARD çalışıyorken PREMIUM eklemek onu bozabilir.

  • Test yazmak giderek zorlaşıyor — her senaryoyu aynı metodun içinden test etmek zorundasınız.

  • Kodu ilk yazan kişi gidince kimse o zincire dokunmak istemiyor.

Zamanla bu metod bir "dokunmaya korkulan yer" haline geliyor. Tanıdık geldi mi?

✅ Şimdi doğrusunu yapalım

Her ücret modelini kendi sınıfına taşıyoruz. Hepsinin uyması gereken tek bir kontrat var:

// Tüm ücret stratejilerinin uyması gereken kontrat
interface FeeStrategy {
    BigDecimal calculate(BigDecimal amount); // Ücret stratejisi
}
// Standart müşteriler — %0.1 oran
class StandardFee implements FeeStrategy {
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.001"));
        // 1000 TL transfer → 1 TL ücret
    }
}

// Premium müşteriler — %0.05 indirimli oran
class PremiumFee implements FeeStrategy {
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.0005"));
        // 1000 TL transfer → 0.50 TL ücret
    }
}

// Kripto transferler — her işlemde sabit $2.50
class CryptoFee implements FeeStrategy {
    public BigDecimal calculate(BigDecimal amount) {
        return new BigDecimal("2.50");
    }
}
// FeeEngine ise hiç değişmeden tüm stratejileri yönetiyor:

// Strateji map'i — FeeEngine yeni strateji gelince değişmez
class FeeEngine {
    private final Map<String, FeeStrategy> strategies = new HashMap<>();

    void register(String name, FeeStrategy strategy) {
        strategies.put(name, strategy);         // yeni strateji kaydı
    }

    BigDecimal calculate(String name, BigDecimal amount) {
        if (!strategies.containsKey(name))
            throw new IllegalArgumentException("Bilinmeyen strateji: " + name);
        return strategies.get(name).calculate(amount);
    }
}



// Kullanalım

FeeEngine engine = new FeeEngine();
engine.register("STANDARD", new StandardFee());
engine.register("PREMIUM",  new PremiumFee());
engine.register("CRYPTO",   new CryptoFee());

BigDecimal amount = new BigDecimal("1000");

System.out.println(engine.calculate("STANDARD", amount)); // 1.00
System.out.println(engine.calculate("PREMIUM",  amount)); // 0.50
System.out.println(engine.calculate("CRYPTO",   amount)); // 2.50

O senaryoya dönelim — kurumsal müşteriler geliyor

İş birimi kapıya dayandı: "Kurumsal müşteriler için 10.000 TL üzeri transferlerde %0.03, altında %0.07 olsun."

// Tek yapmanız gereken şu:
// Yeni sınıf — başka hiçbir şeye dokunmadık
class CorporateFee implements FeeStrategy {
    private static final BigDecimal THRESHOLD  = new BigDecimal("10000");
    private static final BigDecimal HIGH_RATE  = new BigDecimal("0.0003");
    private static final BigDecimal LOW_RATE   = new BigDecimal("0.0007");

    public BigDecimal calculate(BigDecimal amount) {
        BigDecimal rate = amount.compareTo(THRESHOLD) > 0
            ? HIGH_RATE   // 10K üzeri → %0.03
            : LOW_RATE;   // 10K altı  → %0.07
        return amount.multiply(rate);
    }
}

// Tek ekleme bu kadar
engine.register("CORPORATE", new CorporateFee());

StandardFee'ye dokunmadınız. PremiumFee'ye dokunmadınız. FeeEngine'e dokunmadınız. Mevcut testleriniz hâlâ yeşil. Yeni kodu sadece kendi testleriyle test ettiniz ve hazır.

İşte OCP'nin verdiği güvence bu.

Kısaca

Yeni davranış eklemek için mevcut kodu değiştirmek zorunda kalmıyorsunuz. Her strateji kendi sınıfında izole yaşıyor. Biri bozulsa diğerleri etkilenmiyor.

Bir sonraki bölümde Liskov Substitution Principle'a bakacağız — SavingsAccount ile CheckingAccount her yerde Account yerine sorunsuz geçebiliyor mu, birlikte kontrol edeceğiz.

Seriyi kaçırmamak için takipte kalın.