Menü

Neominal

BT Hizmetleri ve Danışmanlığı

5 Mayıs 20265 dk okuma10 görüntüleme
Part 4/5: Interface Segregation Principle

Şöyle bir senaryo düşünelim. Yeni bir geliştirici ekibe katıldı, InvestmentAccount sınıfını yazması gerekiyor. Mevcut BankAccount arayüzünü implement etmesi söyleniyor. Arayüzü açıyor ve şunu görüyor:

interface BankAccount {
    void credit(BigDecimal amount);
    void debit(BigDecimal amount);
    void applyInterest(BigDecimal rate);
    BigDecimal getOverdraftLimit();
    void lockForInvestment();
}

Geliştirici düşünüyor: yatırım hesabından para çekilemiyor, overdraft yok, faiz bu hesabı ilgilendirmiyor. Ama arayüz bunların hepsini istiyor. Ne yapıyor?

public void debit(BigDecimal amount) {
    throw new UnsupportedOperationException("Yatırım hesabından çekim yapılamaz");
}

public void applyInterest(BigDecimal rate) {
    throw new UnsupportedOperationException("Bu hesapta faiz işlemiyor");
}

public BigDecimal getOverdraftLimit() {
    throw new UnsupportedOperationException("Overdraft yok");
}

Üç tane boş — ya da exception fırlatan — metod yazdı. Arayüz onu buna zorladı.

Bir sınıf kullanmadığı metodları implement etmek zorunda kalıyorsa, arayüz yanlış tasarlanmış demektir.

Nedir bu prensip aslında?

Interface Segregation Principle'ın söylediği şey şu: sınıflar kullanmadıkları metodlara bağımlı olmak zorunda kalmamalı.

Büyük, şişirilmiş bir arayüz yerine küçük ve odaklı arayüzler tercih etmeliyiz. Her sınıf yalnızca ihtiyacı olanı alır, fazlasını değil.

❌ Önce yanlışı görelim

Tek bir şişirilmiş arayüz — her şeyi herkese dayatıyor:

// Şişirilmiş arayüz — her hesap tipine her şeyi dayatıyor
interface BankAccount {
    void credit(BigDecimal amount);         // Para yatır
    void debit(BigDecimal amount);          // Para çek
    void applyInterest(BigDecimal rate);    // Faiz uygula
    BigDecimal getOverdraftLimit();         // Overdraft limiti
    void lockForInvestment();               // Yatırıma kilitle
}

// Yatırım hesabı — ama arayüz her şeyi istiyor
class InvestmentAccount implements BankAccount {

    public void credit(BigDecimal amount) {
        // Bunu kullanıyoruz — tamam
    }

    public void debit(BigDecimal amount) {
        throw new UnsupportedOperationException("Çekim yapılamaz"); // ❌
    }

    public void applyInterest(BigDecimal rate) {
        throw new UnsupportedOperationException("Faiz yok");        // ❌
    }

    public BigDecimal getOverdraftLimit() {
        throw new UnsupportedOperationException("Overdraft yok");   // ❌
    }

    public void lockForInvestment() {
        // Belki bunu kullanıyoruz
    }
}

Bu sınıfı test etmeye çalışın. Hangi metodun gerçekten çalıştığını, hangisinin exception fırlattığını her seferinde hatırlamanız gerekiyor. Üstelik BankAccount arayüzüne yeni bir metod eklendiğinde — ki ekleniyor, hep ekleniyor — bu sınıfa da dokunmak zorunda kalıyorsunuz.

✅ Şimdi doğrusunu yapalım

Her sorumluluğu kendi küçük arayüzüne taşıyoruz:

// Para yatırılabilir hesaplar için
interface Creditable {
    void credit(BigDecimal amount);         // Para yatır
}

// Para çekilebilir hesaplar için
interface Debitable {
    void debit(BigDecimal amount);          // Para çek
}

// Faiz işleyebilen hesaplar için
interface InterestBearing {
    void applyInterest(BigDecimal rate);    // Faiz uygula
    BigDecimal getAccruedInterest();        // Birikmiş faiz
}

Şimdi soyut taban sınıfımız ve her hesap tipi kendi ihtiyacına göre şekilleniyor:

// Ortak alan ve davranışlar burada
abstract class Account {
    protected BigDecimal balance;

    Account(BigDecimal balance) {
        this.balance = balance;
    }

    BigDecimal getBalance() { return balance; }
}

// Tasarruf hesabı — her şeye ihtiyacı var
class SavingsAccount extends Account
        implements Creditable, Debitable, InterestBearing {

    private BigDecimal accruedInterest = BigDecimal.ZERO;
    private int monthlyWithdrawals = 0;
    private static final int LIMIT = 6;

    SavingsAccount(BigDecimal balance) { super(balance); }

    @Override
    public void credit(BigDecimal amount) {             // Para yatır
        balance = balance.add(amount);
    }

    @Override
    public void debit(BigDecimal amount) {              // Para çek — limitli
        if (monthlyWithdrawals >= LIMIT)
            throw new IllegalStateException("Aylık çekim limitine ulaşıldı");
        if (amount.compareTo(balance) > 0)
            throw new IllegalStateException("Yetersiz bakiye");
        balance = balance.subtract(amount);
        monthlyWithdrawals++;
    }

    @Override
    public void applyInterest(BigDecimal rate) {        // Faiz uygula
        BigDecimal interest = balance.multiply(rate);
        accruedInterest = accruedInterest.add(interest);
        balance = balance.add(interest);
    }

    @Override
    public BigDecimal getAccruedInterest() { return accruedInterest; }
}

// Vadesiz hesap — faiz yok, overdraft var
class CheckingAccount extends Account
        implements Creditable, Debitable {

    private final BigDecimal overdraftLimit;

    CheckingAccount(BigDecimal balance, BigDecimal overdraftLimit) {
        super(balance);
        this.overdraftLimit = overdraftLimit;
    }

    @Override
    public void credit(BigDecimal amount) {             // Para yatır
        balance = balance.add(amount);
    }

    @Override
    public void debit(BigDecimal amount) {              // Para çek — overdraft destekli
        BigDecimal available = balance.add(overdraftLimit);
        if (amount.compareTo(available) > 0)
            throw new IllegalStateException("Overdraft limiti aşıldı");
        balance = balance.subtract(amount);
    }
}

// Yatırım hesabı — sadece para yatırılır, başka bir şey yok
class InvestmentAccount extends Account
        implements Creditable {

    InvestmentAccount(BigDecimal balance) { super(balance); }

    @Override
    public void credit(BigDecimal amount) {             // Sadece para yatır
        balance = balance.add(amount);
    }
    // debit() yok        — çekim yapılamaz, boş metod da yok
    // applyInterest() yok — faiz bu hesabı ilgilendirmiyor
}


Kullanalım

// Tasarruf hesabı — her arayüzden erişilebilir
SavingsAccount savings = new SavingsAccount(new BigDecimal("5000"));
savings.credit(new BigDecimal("1000"));             // bakiye: 6000
savings.debit(new BigDecimal("500"));               // bakiye: 5500
savings.applyInterest(new BigDecimal("0.02"));      // %2 faiz → bakiye: 5610
System.out.println("Faiz: " + savings.getAccruedInterest()); // 110

// Yatırım hesabı — sadece credit görünür
InvestmentAccount investment = new InvestmentAccount(new BigDecimal("10000"));
investment.credit(new BigDecimal("5000"));          // bakiye: 15000
// investment.debit(...)         → derleme hatası — arayüzde yok ✅
// investment.applyInterest(...) → derleme hatası — arayüzde yok ✅

// Arayüz üzerinden polimorfik kullanım
Creditable c = new InvestmentAccount(new BigDecimal("3000"));
c.credit(new BigDecimal("1000"));                   // sadece credit görünür

Debitable d = new SavingsAccount(new BigDecimal("2000"));
d.debit(new BigDecimal("500"));                     // sadece debit görünür

InterestBearing ib = new SavingsAccount(new BigDecimal("4000"));
ib.applyInterest(new BigDecimal("0.03"));           // sadece faiz metodları görünür

Derleme hatası — aslında bir güvence

"Derleme hatası" kulağa kötü geliyor ama burada tam tersi. InvestmentAccount üzerinde debit() çağırmaya çalışırsanız kod derlenmez. Bu bir hata değil, bir güvence. Sistem size "bu işlemi bu hesapta yapamazsın" diyor — runtime'da değil, daha kod çalışmadan söylüyor.

Şişirilmiş arayüzde ise kod derlenir, çalışır, sonra production'da exception fırlatır. Hangisini tercih edersiniz?

Hiyerarşi özeti

Account (abstract)

├── SavingsAccount → Creditable, Debitable, InterestBearing

├── CheckingAccount → Creditable, Debitable

└── InvestmentAccount → Creditable

Her sınıf yalnızca ihtiyacı olan arayüzü alıyor. Kimse kullanmadığı bir metodu implement etmek zorunda kalmıyor.

Kısaca

Büyük arayüzler küçük küçük parçalara bölündüğünde her sınıf gerçekten ihtiyacı olanı alır. Boş metod yok, exception fırlatan sahte implementasyon yok. Derleme zamanında tip güvenliği kazanılıyor ve kod çok daha net okunuyor.

Serinin son bölümünde Dependency Inversion Principle'a bakacağız — TransferOrchestrator hiçbir somut sınıfı tanımadan nasıl çalışıyor, birlikte göreceğiz.

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