Nesne Yönelimli Tasarım — Kapsülleme ve Kompozisyon
Önce şunu oku: SOLID — Beş Harf, Tek Soru
30 saniyede özet
Nesne yönelimli tasarımın dört ilkesini saymak kolay. Günlük kodda ise iki karar verirsin: bu nesne neyi korumalı, ve bir şeyi yeniden kullanmak için ondan mı türeyeceğim, yoksa onu içime mi koyacağım?
Dört ilkeyi ezbere saymak kod yazarken pek yardım etmez. Günlük kodda iki karar verirsin: bu nesne neyi koruyor, ve yeniden kullanmak için kalıtım mı, kompozisyon mu.
Adım adım oku
- Solda alan private ama bir setter'ı var: kapı kilitli görünüyor, anahtar ise herkeste.
- Dışarıdaki bir kod setBalance(-500) çağırır ve bakiye eksiye düşer. Kuralı koruyan kimse yok.
- Sağda nesnenin setter'ı yok; yalnızca withdraw davranışı var ve kuralı o bilir.
- withdraw(500) bakiye yetersiz diye reddedilir. Kural nesnenin içinde, dışarıdan bozulamaz.
-
Bayt: Bütün alanları private yaptım, her birine getter ve setter ekledim. Kapsülledim!
-
Sen: Harika, artık kimse alanlara dokunamaz.
-
Bayt: Ama biri setBalance(-500) çağırdı ve hesap eksiye düştü!
-
Bayt: Kapsülleme saklamak değil, korumaktır. Kuralı kim koruyor, ona bakalım.
Kapsülleme: saklamak değil, korumak
Yaygın okunuş şu: alanları private yap, getter/setter ekle. Bu kapsülleme değil, alanları daha uzun yoldan public yapmaktır.
class Account { private BigDecimal balance; public BigDecimal getBalance() { return balance; } public void setBalance(BigDecimal b) { this.balance = b; }}
class Account { private BigDecimal balance; public BigDecimal balance() { return balance; } public void withdraw(BigDecimal amount) { if (amount.compareTo(balance) > 0) throw new InsufficientFunds(); balance = balance.subtract(amount); }}Fark şurada: ilkinde bakiyeyi eksiye düşürmek çağıranın elinde. İkincisinde düşüremezsin — kural nesnenin içinde ve dışarıdan atlanamıyor.
Buna değişmez kuralNesne var olduğu sürece doğru kalması gereken şey. Kapsüllemenin ölçütü kaç alanın private olduğu değil, hangi kuralın dışarıdan bozulamadığıdır.Bir setter, koruduğu kural varsa onu atlanabilir kılar. Kuralı metodun içine koymak (withdraw gibi) nesneyi geçersiz duruma düşürmeyi imkânsız hâle getirir.Sözlükte gör → denir: nesne var olduğu sürece doğru kalması gereken şey. Kapsüllemenin ölçütü kaç alanın private olduğu değil, hangi kuralın dışarıdan bozulamadığıdır.
Kafam karıştı, daha basit anlat
Getter ve setter koymak kapıya kilit takıp anahtarı herkese dağıtmaktır. Asıl koruma, “para çek” gibi kuralı bilen bir metottur: bakiye eksiye düşemez.
Kural nerede yaşıyor?
Tohum 360355Bakiye: 200 TL
Oynat ya da adımla: her adımda bir çağıran para çeker.
Şu an ne oldu?
getBalance / setBalance
Bakiye 200 TL. Üç çağıran sırayla para çekecek.
Görevler0/3
Bakiyeyi eksiye düşüraçık
İpucu
Varsayılan ayarlar yeter.
Bakiyeyi koru ama kuralı üç ayrı yerde yazaçık
İpucu
Herkes hatırlasın.
Masraf işi unutsa bile bakiyeyi koruaçık
İpucu
Kuralı nesnenin içine taşı.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat. getBalance/setBalance: ATM ve uygulama kontrol etti, gece masraf işi unuttu ve bakiye -30 TL oldu.
- “Gece masraf işi de bakiyeye bakıyor”u aç. Bakiye korundu, ama kural artık üç ayrı yerde yazılı.
- Anahtarı kapat ve “withdraw” seç. Masraf işi unutsa bile withdraw reddetti; kural tek yerde.
Kapsüllemenin ölçütü nedir?
Soyutlama ile kapsülleme arasındaki fark nedir?
Satır satır: kalıtım neyi devralır?
extends yazmak kod devralmak değil, sözleşme devralmaktır. Üst sınıfın verdiği her sözü tutmak zorundasın — istemediklerini de.
`Stack extends ArrayList` yazarsan LIFO garantisi korunur mu? Cevabı göster
Korunmaz. ArrayList’in add(int index, E e) metodu da miras gelir ve çağıran onu kullanarak yığının ortasına eleman sokabilir.
Devralınan istenmeyen sözler
class Stack<E> extends ArrayList<E> { void push(E e) { add(e); } E pop() { return remove(size() - 1); }} Stack<String> s = new Stack<>();s.push("a");s.push("b");s.add(0, "araya girdim");System.out.println(s.pop());Debug
tasarım Amaç kod tekrarından kaçınmak: ArrayList zaten dinamik dizi tutuyor, onu kullanalım.
- devralınan
- = ArrayList'in tüm public API'si
Sol/sağ ok tuşlarıyla da gezebilirsin.
Kural şu: alt tip gerçekten üst tipin bir çeşidiyse (“is-a”) ve onun her sözünü tutabiliyorsa kalıtım kullan. Sadece koda ulaşmak için kalıtım kullanmak, istemediğin bir 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 →yi de imzalamak demektir.
Kafam karıştı, daha basit anlat
extends bir evi eşyalarıyla birlikte almaktır. İstemediğin eşyalar da gelir, ve başkaları onları kullanabilir.
`Stack<E> extends ArrayList<E>` yazmanın asıl sorunu nedir?
Kompozisyon nerede kazanır?
| Kalıtım | Kompozisyon | |
|---|---|---|
| Bağ ne zaman kurulur | Derleme zamanı, sabit | Çalışma zamanı, değiştirilebilir |
| Devralınan | Üst sınıfın tüm API’si | Yalnızca kullandığın metotlar |
| Değişimin etkisi | Üst sınıf değişirse alt sınıflar kırılabilir | Arayüz aynı kaldıkça etkilenmez |
| Test | Üst sınıfı da kurmak gerekir | Bağımlılığı sahteyle değiştirirsin |
| Çoklu davranış | Java’da tek kalıtım | İstediğin kadar parça birleştirilir |
Bu yüzden varsayılan tercih kompozisyonBir nesnenin ihtiyacı olan davranışı devralmak yerine alan olarak tutması. Yalnızca kullandığın metotları alırsın, üst sınıfın tüm API'sini değil.Varsayılan tercih olmasının sebebi kalıtımın yanlış olması değil, geri alınmasının pahalı olması: bir hiyerarşiyi bozmak bir alanı silmekten çok daha zordur.Sözlükte gör →dur. Kalıtım yanlış olduğu için değil, geri alması pahalı olduğu için: bir hiyerarşiyi bozmak, bir alanı silmekten çok daha zordur.
Bir kredi kartında görelim. Müşteri Classic karttan Gold’a geçtiğinde kart aynı kalır, yalnızca nakit avans ücretinin kuralı değişir.
Derinleş · Kredi kartı: ücret kuralı içeride, değiştirilebilir 4 dosya · ~54 satır · ilk okumada atlayabilirsin
Her durumu, kalıtımın mı kompozisyonun mu doğru araç olduğuna göre ayır.
Polimorfizm: çağıran bilmesin
Polimorfizmin kazancı “aynı metot farklı davranır” değil. Kazanç, çağıranın hangi tip olduğunu bilmek zorunda olmaması.
// yeni bir kanal eklemek bu metodu açtırıyorif (type == EMAIL) sendEmail(m);else if (type == SMS) sendSms(m);
// yeni bir kanal, yeni bir sınıf — bu satır hiç değişmezchannel.send(m);Test basit: yeni bir tip eklediğinde çağıran kodu değiştirmen gerekiyor mu? Gerekmiyorsa doğru yerden bölmüşsündür.
Kafam karıştı, daha basit anlat
Çağıran kod karşısındakinin türünü sormamalı, sadece “gönder” demeli. Yeni bir kanal eklemek, çağıranı değiştirmeden mümkün olur.
Polimorfizmi doğru kullandığını nasıl anlarsın?
Tuzaklar
Kansız (anemik) model. Her alanı getter/setter’lı bir sınıf ve bütün kuralları bilen ayrı bir Service. Bu nesne yönelimli değil; veri bir yerde, kural başka yerde.
Çalışır, ama kural nerede diye aradığında her yerde arayacaksın. Kuralı, koruduğu verinin yanına koy.
Derin hiyerarşi. Üç seviyeden sonra bir metodun nereden geldiğini bulmak için tüm zinciri okumak gerekir. Üstelik ortadaki bir sınıfı değiştirmek, altındaki her şeyi etkiler.
Kalıtımı kod paylaşmak için kullanmak. Ortak kodun varsa onu bir alana veya yardımcı sınıfa koy. extends bir yeniden kullanım aracı değil, bir tip bildirimidir.
Kurucuda override edilebilir metot çağırmak· istersen atla
Üst sınıfın kurucusu, alt sınıfın override ettiği bir metodu çağırırsa o metot alt sınıfın alanları henüz atanmadan çalışır. Sonuç: beklenmedik null veya sıfır. Kurucudan yalnızca private, static veya final metot çağır.
Kendini sına
Her alana getter ve setter eklemek kapsüllemeyi tamamlar.
Bu program ne yazdırır?
Aklında kalacak üç şey
- 1 Kapsülleme getter ve setter yazmak değil, nesnenin geçerli kalmasını sağlayan kuralı dışarı sızdırmamaktır. Her alana setter koyan sınıf hiçbir şeyi korumaz.
- 2 extends yazdığın an üst sınıfın verdiği her sözü tutmak zorundasın. Bu yüzden ilk tercih kompozisyondur.
- 3 Polimorfizmin kazancı, çağıranın ayrıntıyı bilmemesidir: yeni bir tip eklemek çağıran kodu değiştirmiyorsa doğru yerden bölmüşsündür.
5 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.