Kalıtım ve Kompozisyon — Kimin İç Ayrıntısına Bağlısın?
Önce şunu oku: Sınıf, Nesne ve Kapsülleme — Kuralı Kim Koruyor?
30 saniyede özet
extends ile bir sınıfın sadece yüzünü değil, iç işleyişini de miras alırsın; o iç işleyiş değişince senin sınıfın da bozulur. Gerçekten 'bir türü' değilsen, nesneyi içine koy ve işi ona yaptır.
Bir kutuya kaç şey attığını saymak istedin. Hazır bir kutuyu genişletip saymayı ekledin, üç şey attın ve sayaç tutmadı.
-
Bayt: Hazır bir kümeyi genişletip eklenenleri saymasını istedim. Kolay iş!
-
Sen: Üç eleman ekledim, ama sayaç tutmuyor.
-
Bayt: Senin kodunda hata yok. Hata, miras aldığın sınıfın içinde saklı olabilir.
-
Bayt: Kiracı mı olacaksın, ev sahibi mi? Önce tesisata bir bakalım.
Java’da o hazır kutu HashSet’ti ve iki metodunu kendine göre değiştirdin. Senin kodunda hata yok; hata, HashSet’in kendi içinde ne yaptığında.
Miras, evin tesisatıyla gelir
kalıtımBir sınıfın başka bir sınıfı extends ile genişletip onun API'sini ve uygulamasını devralması. "B, bir A'dır" ilişkisini ifade eder.Sözlükte gör →, “B bir çeşit A’dır” demektir. Alt sınıf, taban sınıfın dışarıya açık metotlarını miras alır — ama bu metotların içeride birbirini nasıl çağırdığını da.
Taban sınıfın bir metodu, kendi başka bir metodunu çağırıyor olabilir. Sen bunlardan birini değiştirdiğinde (override), farkında olmadan öbürünün davranışını da değiştirirsin.
Kafam karıştı, daha basit anlat
Bir evi miras almak, tesisatını da almaktır. Bir prizi değiştirdiğinde, duvarın içinde ona bağlı başka bir şeyi de etkileyebilirsin.
Kalıtım ile kompozisyon arasındaki temel fark nedir?
Kırılgan taban sınıf
CountingSet extends HashSet, add ve addAll'u override edip sayıyor. addAll(a, b, c) sonrası sayaç kaç? Cevabı göster
6. HashSet.addAll içeride her eleman için add() çağırır. O çağrı senin override ettiğin add’e düşer ve her eleman ikinci kez sayılır.
Adım adım oku
- addAll(a, b, c) çağrılır ve senin addAll'un sayacı 3 artırır.
- HashSet'in kendi addAll'u, elemanları eklerken içeride add'i çağırır.
- O add senin override ettiğin add'dir ve her elemanı bir kez daha sayar: sayaç 6.
- Sarmalayan sınıfta ise çağrı içteki kümeye iletilir; içteki kümenin kendi add çağrıları senin sayacına hiç uğramaz: 3.
addAll’u override etmeyi bırakırsan sayaç doğru çıkar — ama yalnızca bugün. Kodun artık HashSet’in içinin nasıl yazıldığına güveniyor. HashSet’in yeni bir sürümü addAll’u değiştirirse, senin kodun tek satırı değişmeden bozulur.
Buna fragile base classTaban sınıftaki masum görünen bir değişikliğin, onun iç ayrıntısına güvenen alt sınıfları sessizce bozması.Sözlükte gör → (kırılgan taban sınıf) denir: üstünde durduğun zemin, sana sormadan kayabilir.
Kafam karıştı, daha basit anlat
addAll içeride senin add metodunu çağırıyordu, sen bunu bilmiyordun. Her eleman iki kez sayıldı. Taban sınıfın iç düzeni değişirse senin kodun da değişir.
`class CountingSet extends HashSet` hem `add` hem `addAll`'u override ederek sayıyor. `addAll(List.of("a","b","c"))` sonrası sayaç neden 6 oluyor?
Çift sayımı düzeltmek için yalnızca `add`'i override ettin; testler geçti. Neden bu hâlâ kırılgan?
Kendin gör
Kırılgan taban sınıf — kalıtım mı, kompozisyon mu?
Tohum 943283class CountingSet<E> extends HashSet<E> { private int addCount; @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c) { addCount += c.size(); return super.addAll(c); }}- Oynat ya da adımla.
Şu an ne oldu?
CountingSet: kaç eleman eklendi?
addAll ile 3, add ile 1 eleman eklenecek. Doğru cevap 4.
Görevler0/3
Aynı elemanları iki kez sayaçık
İpucu
Hem add hem addAll sayıyor, ve taban sınıfın addAll'u içeride add çağırıyor.
Kendi kodunu değiştirmeden sayacı bozaçık
İpucu
Taban sınıfın yeni sürümü iç ayrıntısını değiştirirse?
Her iki taban sürümünde de doğru sayan tasarımı bulaçık
İpucu
Taban sınıfın içine hiç bağlı olmayan bir tasarım.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat. İki metot override edilmiş: 7 sayıldı, 4 eklendi.
- Tasarımı “yalnızca add” yap. 4 — doğru.
- Taban sınıfı v2 yap. Aynı kod, sayaç 1.
- Tasarımı “iç sete yönlendirir” yap. İki sürümde de 4.
Her ilişki kalıtım için mi kompozisyon için mi daha uygun?
Satır satır: sar ve yönlendir
İç seti yalnızca sözleşmesiyle kullan
class CountingSet<E> implements Set<E> { private final Set<E> inner; private int addCount; CountingSet(Set<E> inner) { this.inner = inner; } public boolean add(E e) { addCount++; return inner.add(e); } public boolean addAll(Collection<? extends E> c) { addCount += c.size(); return inner.addAll(c); } // contains, remove, size… hepsi inner'a yönlendirilir}Debug
tasarım Set arayüzünü uyguluyor: Set bekleyen her yere verilebilir. HashSet'ten türemiyor.
Sol/sağ ok tuşlarıyla da gezebilirsin.
CountingSet artık HashSet’ten türemiyor; içinde bir HashSet tutuyor ve işi ona yaptırıyor. Bu yönlendirmeye delegationBir nesnenin işi, içinde tuttuğu başka bir nesneye yönlendirmesi (forwarding). Kompozisyonun uygulanış biçimi.Sözlükte gör → denir. Artık iç setin nasıl çalıştığı değil, yalnızca ne yaptığı önemli.
Aynı tuzak bankada daha pahalıya patlar. Takas sağlayıcısının hazır ödeme sınıfını genişletip müşterinin günlük limitini saymaya çalışırsan, toplu ödemeler iki kez sayılır. Müşteri limitinin yarısında engellenir.
Derinleş · Günlük ödeme limiti: genişletmek yerine sarmak 5 dosya · ~115 satır · ilk okumada atlayabilirsin
Kafam karıştı, daha basit anlat
Evi miras almak yerine bir çalışan tut: işi ona yaptır, sen yalnızca saymayı yap. Onun içeride nasıl çalıştığı seni artık ilgilendirmez.
Bu sınıf "yalnızca geçerli e-posta adresleri" tutmak için yazıldı ama geçersiz adresler içine girebiliyor. Hangi satır tasarım hatası?
Kompozisyonun en sık dile getirilen dezavantajı nedir ve nasıl hafifletilir?
Tuzaklar
Sadece kod paylaşmak için extends. Java’daki Stack extends Vector bunun ünlü örneği: yığına yalnızca üstten eklenmeliyken, Vector’dan gelen metotlarla ortasına da eklenebiliyor.
Miras alınmak için tasarlanmamış bir sınıfı açık bırakmak. Ya metotlarının birbirini nasıl çağırdığını belgele, ya da sınıfı final yapıp türetmeyi kapat.
Miras gelen arka kapılar. EmailList extends ArrayList e-posta kontrolünü add’e koyar, ama miras gelen set ve addAll kontrolsüz kalır.
Derin aile ağaçları. Üç kuşak yukarıdaki bir sınıfta yapılan küçük bir değişiklik, en alttaki sınıfı kırabilir.
Kendini sına
Kalıtım, taban sınıfın metotlarının içeride birbirini nasıl çağırdığına da bağlanmak demektir.
Başkalarının genişletmesi için tasarlamadığın bir public sınıf yazıyorsun. En güvenli varsayılan hangisi?
Aklında kalacak üç şey
- 1 Kalıtım, taban sınıfın içini de miras alır. Taban sınıfın kendi metotlarını nasıl kullandığı, alt sınıfın doğru çalışıp çalışmayacağını belirler.
- 2 Taban sınıfın masum bir güncellemesi alt sınıfı sessizce bozabilir. Buna kırılgan taban sınıf denir.
- 3 Kompozisyonda içteki nesneyi yalnızca dışarıya söz verdiği metotlarla kullanırsın. Gerçek bir 'bir türüdür' ilişkisi yoksa ilk tercih budur.
5 kart sonraki derste seni bekliyor