İçeriğe geç

SOLID — Beş Harf, Tek Soru

Orta 10 dk Çok sık karşılaşılır

Önce şunu oku: Temiz Kod — İsimler, Fonksiyonlar, Yorumlar

30 saniyede özet

SOLID beş ayrı kural gibi görünür ama hepsi aynı soruyu sorar: bu kod değişince başka kaç yere dokunmam gerekecek? Beşi de bu sayıyı küçültmenin farklı yollarıdır.

Evde bir lambayı değiştirmek için bütün evin elektriğini kesmek zorunda kaldığını düşün. Can sıkıcı, değil mi? Yazılımda da bazen tek bir şeyi değiştirmek, alakasız üç şeyi birden karartır.

Beş harfi bir anda ezberlemeyeceğiz. Harf harf gideceğiz, her harf küçük bir adım. Sonunda beşini yan yana koyunca hepsinin aynı soruyu sorduğunu göreceksin.

Aynı tamirat, iki ev: biri bütün odaları karartır, öteki yalnızca birini.
Adım adım oku
  1. Değişmesi gereken şey rapor formatı.
  2. Her şeyin tek bir sınıfta yaşadığı evde tek sigorta var: rapora dokunmak sipariş ve e-postayı da karartır.
  3. Her sorumluluğun kendi sınıfı olduğu evde yalnızca rapor odası kararır; öteki odalar yanmaya devam eder.
  4. SOLID'in beş harfi hep aynı soruyu sorar: bu değişim başka nereye dokunuyor?
  1. Bayt: Rapor formatını değiştirdim, sipariş e-postaları bozuldu!

  2. Sen: İkisinin ne alakası var?

  3. Bayt: Aynı sınıfta yaşıyorlarsa alakaları var: aynı sigortaya bağlılar.

  4. Bayt: Beşini birden ezberleme. Harf harf gidelim, her harfte küçük bir kazanç.

S — Single Responsibility

Single Responsibility Principle
Tek Sorumluluk İlkesi: bir sınıfın değişmek için tek bir sebebi olsun.

Küçük bir lokanta düşün: aşçı hem yemek yapıyor hem muhasebe tutuyor. Vergi kuralı değişince mutfak da durur.

Önce: iki patron, tek sınıf
class ReportService {
String format(Sales s) { /* muhasebe ne isterse */ }
void email(String body) { /* BT ne isterse */ }
}
Sonra: her patronun kendi sınıfı
class ReportFormatter { String format(Sales s) { /* ... */ } }
class ReportMailer { void send(String body) { /* ... */ } }
Kafam karıştı, daha basit anlat

Bir sınıfı kimin değiştirmek isteyeceğini düşün. Tek bir cevap geliyorsa iyi, iki farklı ekip geliyorsa ikiye böl.

Hızlı kontrolBaşlangıç

SRP'deki "tek sorumluluk" ne demek?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

O — Open/Closed

Open/Closed Principle
Açık/Kapalı İlkesi: yeni şey eklemeye açık, çalışan kodu değiştirmeye kapalı.

Çalışan bir dosyayı her yeni istekte yeniden açmak, her misafir geldiğinde duvar yıkmak gibidir. OCP, yeni şeyin yeni bir dosya olarak eklenebilmesini ister.

Yeni bir ödeme tipi (kripto) eklemek için kaç dosyaya dokunmak gerekiyor? Cevabı göster

Mevcut hâlde en az iki: PaymentProcessor (yeni else if) ve testleri. Dosya değiştiği için eski dalları da yeniden test etmek gerekir.

Yeni tip eklemek neye mal oluyor?

PaymentProcessor.java
1void process(Payment p) {
şu an çalışan satır if (p.type() == CARD) {
3 cardGateway.charge(p);
4 } else if (p.type() == TRANSFER) {
5 bank.transfer(p);
6 } else if (p.type() == WALLET) {
7 wallet.debit(p);
8 } else {
9 throw new IllegalArgumentException();
10 }
11}

Debug

Adım 1/5

bugün Üç ödeme tipi, üç dal. Bu hâliyle kod tamamen okunur ve sorun yok.

tip sayısı
= 3
dokunulan dosya
= 1
Java 21UTF-8LF2:1

Sol/sağ ok tuşlarıyla da gezebilirsin.

Kafam karıştı, daha basit anlat

Çekmecesi olan bir dolap düşün. Yeni bir şey koymak için dolabı sökmezsin, yeni bir çekmece takarsın.

Her yeni ödeme tipi neye mal oluyor?

Tohum 487319

    Oynat ya da adımla: her adımda yeni bir ödeme tipi gelir.

    Hız
    Adım 0

    Şu an ne oldu?

    if-else zinciri (tek PaymentProcessor)

    Bugün 3 ödeme tipi var; 5 yeni tip sırayla gelecek.

    Görevler0/2

    • Çalışan bir dosyayı on kez yeniden açaçık

      İpucu

      Varsayılan ayarlar yeter.

    • Beş yeni tipi, çalışan hiçbir dosyaya dokunmadan ekleaçık

      İpucu

      Her tip kendi sınıfında.

    Olay günlüğü (0)

    Henüz olay yok. Oynat veya adımla.

    1. Varsayılanla oynat. if-else zincirinde beş yeni tip, çalışan PaymentProcessor’ı ve testini her seferinde yeniden açtı.
    2. “PaymentHandler arayüzü” seç. Aynı beş tip yeni sınıflar olarak geldi; çalışan hiçbir dosyaya dokunulmadı.
    Hızlı kontrolOrta

    Üç dallı bir `if/else` zincirini hemen Strategy'ye çevirmeli misin?

    Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

    L — Liskov Substitution

    Liskov Substitution Principle
    Liskov Yerine Geçme İlkesi: alt sınıf, üst sınıfın yerine sessizce geçebilmeli.

    Liskov ikamesiAlt tip, üst tipin kullanıldığı her yerde sorunsuz yerine geçebilmeli. Ölçüt "gerçek hayatta öyle mi" değil, "üst tipi kullanan kod alt tiple de doğru çalışır mı".Sözlükte gör → ilkesi şunu der: alt sınıf, üst sınıfın yerine geçtiğinde kimse fark etmemeli. Derleyici bunu göremez, çünkü bozulan şey imza değil, verilen sözdür.

    Kare bir dikdörtgen midir?
    class Rectangle {
    void setWidth(int w) { this.w = w; }
    void setHeight(int h) { this.h = h; }
    int area() { return w * h; }
    }
    class Square extends Rectangle {
    void setWidth(int w) { this.w = w; this.h = w; } // sözü bozdu
    void setHeight(int h) { this.h = h; this.w = h; }
    }

    Rectangle kullanan kod “genişliği değiştirirsem yükseklik sabit kalır” diye bir söz almıştı. Square bu sözü tutmuyor.

    Bankada da aynısı olur: vadeli hesap da bir hesaptır ama vadesi dolmadan para çekilemez. Onu “çekilebilir” gibi göstermek, üst tipin sözleşmeBir metodun çağırana verdiği söz: neyi kabul eder, ne döndürür, hangi hataları atar. İmzada görünmeyen kısmı da sözleşmenin parçasıdır.Liskov ihlalleri neredeyse her zaman imzayı değil sözleşmeyi bozar — bu yüzden derleyici onları yakalayamaz.Sözlükte gör →sini daraltır.

    Derinleş · Vadesiz ve vadeli hesap: sözü daraltmadan 4 dosya · ~82 satır · ilk okumada atlayabilirsin
    Proje dosyaları

    src/main/java/com/bank/account/ Withdrawable.java Sözleşme: pozitif her tutarı kabul eder, yalnızca bakiye yetmezse reddeder.

    src/main/java/com/bank/account/Withdrawable.java
    /**
    * Accepts any positive amount in the account's currency.
    * Fails only with InsufficientFundsException when the balance is too low.
    */
    public interface Withdrawable {
    Money balance();
    void withdraw(Money amount);
    }

    src/main/java/com/bank/account/ DemandDepositAccount.java Vadesiz hesap bu sözü eksiksiz tutar.

    src/main/java/com/bank/account/DemandDepositAccount.java
    public class DemandDepositAccount implements Withdrawable {
    private final Iban iban;
    private Money balance;
    public DemandDepositAccount(Iban iban, Money openingBalance) {
    this.iban = iban;
    this.balance = openingBalance;
    }
    @Override
    public Money balance() {
    return balance;
    }
    @Override
    public void withdraw(Money amount) {
    if (!amount.isPositive()) {
    throw new IllegalArgumentException("amount must be positive");
    }
    if (amount.isGreaterThan(balance)) {
    throw new InsufficientFundsException(iban, amount);
    }
    balance = balance.minus(amount);
    }
    }

    src/main/java/com/bank/account/ TimeDepositAccount.java Vadeli hesap bu sözü hiç vermez; kendi sözü vade sonundaki ödemedir.

    src/main/java/com/bank/account/TimeDepositAccount.java
    // Not Withdrawable, and it never claims to be. Its own promise is the payout at maturity.
    public final class TimeDepositAccount {
    private static final BigDecimal DAYS_IN_YEAR = BigDecimal.valueOf(365);
    private final Money principal;
    private final BigDecimal annualRate;
    private final LocalDate openedOn;
    private final LocalDate maturesOn;
    public TimeDepositAccount(Money principal, BigDecimal annualRate, LocalDate openedOn, LocalDate maturesOn) {
    this.principal = principal;
    this.annualRate = annualRate;
    this.openedOn = openedOn;
    this.maturesOn = maturesOn;
    }
    // Simple interest for the term, before withholding tax.
    public Money payoutAtMaturity() {
    long days = ChronoUnit.DAYS.between(openedOn, maturesOn);
    BigDecimal interest = principal.value()
    .multiply(annualRate)
    .multiply(BigDecimal.valueOf(days))
    .divide(DAYS_IN_YEAR, 2, RoundingMode.HALF_UP);
    return new Money(principal.value().add(interest), principal.currency());
    }
    }

    src/main/java/com/bank/account/legacy/ TimeDepositAccount.java Yanlış yol: vadesizden türeyip çekimi kapatmak. ATM sözleşmede olmayan bir istisnayla karşılaşır.

    src/main/java/com/bank/account/legacy/TimeDepositAccount.java
    // Another way to write it: reuse the demand account and switch withdrawals off.
    public class TimeDepositAccount extends DemandDepositAccount {
    private final LocalDate maturesOn;
    public TimeDepositAccount(Iban iban, Money principal, LocalDate maturesOn) {
    super(iban, principal);
    this.maturesOn = maturesOn;
    }
    @Override
    public void withdraw(Money amount) {
    // Code written against Withdrawable, such as the ATM, never expects this exception.
    if (LocalDate.now().isBefore(maturesOn)) {
    throw new UnsupportedOperationException("locked until " + maturesOn);
    }
    super.withdraw(amount);
    }
    }
    Kafam karıştı, daha basit anlat

    “Ben de bir hesabım” diyen her şey, hesaptan beklenen her işi yapabilmeli. Yapamıyorsa hesap gibi davranmasın, kendi adıyla dursun.

    Hızlı kontrolOrta

    Bir alt sınıf üst sınıfın kabul ettiği bir girdiyi reddedip istisna atıyor. Bu hangi ilkeyi bozar?

    Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

    I — Interface Segregation

    Interface Segregation Principle
    Arayüz Ayrımı İlkesi: kimse kullanmadığı metotları taşımak zorunda kalmasın.

    Altmış tuşlu bir televizyon kumandası düşün; sen yalnızca sesi açmak istiyorsun. Asansör paneli ise sana sadece katları gösterir.

    Interface Segregation, her kullanıcıya yalnızca işine yarayan tuşları vermeyi ister.

    Önce: herkese altmış tuş
    interface ReportStore {
    Report find(ReportId id);
    List<Report> findAll();
    void save(Report report); // read-only clients never call this
    void delete(ReportId id);
    void purgeOlderThan(Duration age); // only the nightly cleanup job does
    }
    Sonra: her kullanıcıya kendi paneli
    interface ReportReader { Report find(ReportId id); List<Report> findAll(); }
    interface ReportWriter { void save(Report report); void delete(ReportId id); }
    interface ReportMaintenance { void purgeOlderThan(Duration age); }

    Aynı sınıf üçünü birden uygulayabilir. Ama ekrandaki rapor listesi yalnızca ReportReader görür ve yanlışlıkla bir şey silemez.

    Kafam karıştı, daha basit anlat

    Bir sınıf, bir arayüzün yarısını “bu bana lazım değil” diye boş bırakıyorsa, o arayüz fazla büyüktür.

    Hızlı kontrolOrta

    Bu arayüzü uygulayan `ReadOnlyReportStore` sınıfı her metotta exception fırlatmak zorunda kalıyor. Hangi satırlar ISP'yi ihlal eden şişkinliğin kaynağı?

    Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

    Hatalı satıra dokun, sonra kontrol et.

    ReportStore.java
    Java 21UTF-8LF

    D — Dependency Inversion

    Dependency Inversion Principle
    Bağımlılığın Tersine Çevrilmesi İlkesi: iş kuralları altyapıya değil, kendi tanımladığı soyutlamaya bağlansın.

    Duvara bir priz takılı: lambayı değiştirirken duvarı kırmazsın, çünkü ikisi aynı fişe uyar. Dependency Inversion da iş kurallarını veritabanına değil, kendi tanımladığı bir fişe bağlar.

    Ok yönü
    Kötü: OrderService ──► PostgresOrderRepository
    (iş kuralı, altyapıya bağlı)
    İyi: OrderService ──► OrderRepository ◄── PostgresOrderRepository
    (arayüz iş katmanında tanımlı)

    Kritik nokta arayüzün nerede tanımlandığı. Arayüz altyapı paketinde duruyorsa ok yönü değişmemiştir, araya yalnızca bir dosya girmiştir.

    Kafam karıştı, daha basit anlat

    Fişin şeklini prizi kullanan belirler. Duvardaki kablolar değişse de lamba aynı kalır.

    Hızlı kontrolOrta

    Her durumu, hangi SOLID maddesinin işaret ettiğine göre ayır.

    Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

    Sınıflandırılmamış

    SRP veya OCP

    Değişim sebebi ve genişleme ile ilgili

      LSP, ISP veya DIP

      Sözleşme, arayüz boyutu veya bağımlılık yönü

        Hepsi bir arada

        Beş harfi tek tek geçtin. Şimdi yan yana koyalım:

        HarfKısacaAsıl sorusu
        S — Single ResponsibilityTek değişim sebebiBu sınıf kimin isteğiyle değişiyor?
        O — Open/ClosedEklemeye açıkYeni durum eski kodu açtırıyor mu?
        L — Liskov SubstitutionSözü daraltmaAlt sınıf verilen sözü tutuyor mu?
        I — Interface SegregationKüçük arayüzlerKullanıcı boş tuş taşıyor mu?
        D — Dependency InversionOk yönüArayüz kimin paketinde?

        Hepsi aynı soruyu soruyor: bu değiştiğinde başka nelere dokunmam gerekecek?

        Üç küçük uyarı:

        • SOLID bir teşhis aracıdır, hedef değil. Cevap “az yere” ise harfleri saymana gerek yok.
        • Her ilkenin bir bedeli var. OCP dosya, ISP arayüz, DIP dolaylılıkBir şeye doğrudan değil, araya giren bir katman üzerinden ulaşmak. Esneklik verir, okurken bir adım daha attırır — bedava değildir.Sözlükte gör → ekler.
        • Arayüz sayısı başarı değildir. Tek uygulaması olan arayüzü, ikinci uygulama gelince çıkarmak kolaydır.
        Şimşek turu1/5

        Tek sorumluluk ilkesi, bir sınıfın yalnızca tek bir metodu olması demektir.

        Soru 1/4Başlangıç

        SOLID'in beş maddesinin ortak amacı nedir?

        Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

        Aklında kalacak üç şey

        1. 1 SOLID'in beş harfi tek bir soruyu farklı açılardan sorar: bu değişince başka nelere dokunmam gerekecek?
        2. 2 'Tek sorumluluk' tek iş yapmak değil, değişmek için tek bir sebebe sahip olmaktır. Kimin isteğiyle değişiyorsa sorumlusu odur.
        3. 3 Alt sınıf, üst sınıfın sözünü daraltıyorsa (daha az girdi kabul ediyor ya da daha zayıf söz veriyorsa) kalıtım yanlıştır.
        Sonraki kapı Bir şeyi yeniden kullanmak için ondan mı türemeli, onu içine mi koymalı? Nesne Yönelimli Tasarım — Kapsülleme ve Kompozisyon · 10 dk

        5 kart sonraki derste seni bekliyor

        0/5 kart bu dersten toplandı

        Bu dersin üstüne kurulanlar

        Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.