İçeriğe geç

Kalıtım ve Kompozisyon — Kimin İç Ayrıntısına Bağlısın?

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

Ö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ı.

  1. Bayt: Hazır bir kümeyi genişletip eklenenleri saymasını istedim. Kolay iş!

  2. Sen: Üç eleman ekledim, ama sayaç tutmuyor.

  3. Bayt: Senin kodunda hata yok. Hata, miras aldığın sınıfın içinde saklı olabilir.

  4. 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.

Hızlı kontrolBaşlangıç

Kalıtım ile kompozisyon arasındaki temel fark nedir?

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

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.

Miras aldığın sınıfın iç tesisatı, senin sayacına da bağlanır.
Adım adım oku
  1. addAll(a, b, c) çağrılır ve senin addAll'un sayacı 3 artırır.
  2. HashSet'in kendi addAll'u, elemanları eklerken içeride add'i çağırır.
  3. O add senin override ettiğin add'dir ve her elemanı bir kez daha sayar: sayaç 6.
  4. 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.

Hızlı kontrolOrta

`class CountingSet extends HashSet` hem `add` hem `addAll`'u override ederek sayıyor. `addAll(List.of("a","b","c"))` sonrası sayaç neden 6 oluyor?

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

Çift sayımı düzeltmek için yalnızca `add`'i override ettin; testler geçti. Neden bu hâlâ kırılgan?

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

Kendin gör

Kırılgan taban sınıf — kalıtım mı, kompozisyon mu?

Tohum 943283
CountingSet.java
1class CountingSet<E> extends HashSet<E> {
2 private int addCount;
3 @Override public boolean add(E e) { addCount++; return super.add(e); }
4 @Override public boolean addAll(Collection<? extends E> c) {
5 addCount += c.size();
6 return super.addAll(c);
7 }
8}
Java 21UTF-8LF
  1. Oynat ya da adımla.
Hız
Adım 0

Ş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.

  1. Varsayılanla oynat. İki metot override edilmiş: 7 sayıldı, 4 eklendi.
  2. Tasarımı “yalnızca add” yap. 4 — doğru.
  3. Taban sınıfı v2 yap. Aynı kod, sayaç 1.
  4. Tasarımı “iç sete yönlendirir” yap. İki sürümde de 4.
Hızlı kontrolOrta

Her ilişki kalıtım için mi kompozisyon için mi daha uygun?

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

Sınıflandırılmamış

Kalıtım

Gerçekten 'bir türüdür' ve taban sınıf genişletilmek için tasarlanmış

    Kompozisyon

    'Bir ... kullanır' ya da yalnızca kodu yeniden kullanma isteği

      Satır satır: sar ve yönlendir

      İç seti yalnızca sözleşmesiyle kullan

      CountingSet.java
      şu an çalışan satırclass CountingSet<E> implements Set<E> {
      2 private final Set<E> inner;
      3 private int addCount;
      4
      5 CountingSet(Set<E> inner) { this.inner = inner; }
      6
      7 public boolean add(E e) { addCount++; return inner.add(e); }
      8
      9 public boolean addAll(Collection<? extends E> c) {
      10 addCount += c.size();
      11 return inner.addAll(c);
      12 }
      13 // contains, remove, size… hepsi inner'a yönlendirilir
      14}

      Debug

      Adım 1/4

      tasarım Set arayüzünü uyguluyor: Set bekleyen her yere verilebilir. HashSet'ten türemiyor.

      Java 21UTF-8LF1:1

      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
      Proje dosyaları

      src/main/java/com/bank/payment/ Payments.java Ödeme sözleşmesi: bankanın geri kalanı yalnızca bunu tanır.

      src/main/java/com/bank/payment/Payments.java
      public interface Payments {
      PaymentReceipt pay(Payment payment);
      List<PaymentReceipt> payAll(List<Payment> batch);
      }

      src/main/java/com/bank/payment/ ClearingPayments.java Sağlayıcının sınıfını saran ince bir adaptör. Sağlayıcının iç tesisatı burada kalır.

      src/main/java/com/bank/payment/ClearingPayments.java
      // Talks to the clearing provider's SDK through its public methods only.
      final class ClearingPayments implements Payments {
      private final ClearingGateway gateway; // vendor SDK class, not ours to change
      ClearingPayments(ClearingGateway gateway) {
      this.gateway = gateway;
      }
      @Override
      public PaymentReceipt pay(Payment payment) {
      return gateway.pay(payment);
      }
      @Override
      public List<PaymentReceipt> payAll(List<Payment> batch) {
      return gateway.payAll(batch);
      }
      }

      src/main/java/com/bank/payment/ DailyLimitPayments.java Günlük limit, sözleşmeyi uygulayan ve işi içteki ödemeye yönlendiren bir sarmalayıcı. Toplu ödeme bir kez sayılır, ödeme başarısız olursa limit geri verilir.

      src/main/java/com/bank/payment/DailyLimitPayments.java
      // Counts each customer's outgoing payments against a daily limit.
      // A scheduled job clears usedToday at midnight; it is left out here.
      final class DailyLimitPayments implements Payments {
      private final Payments inner;
      private final BigDecimal dailyLimit;
      private final Map<String, BigDecimal> usedToday = new HashMap<>();
      DailyLimitPayments(Payments inner, BigDecimal dailyLimit) {
      this.inner = inner;
      this.dailyLimit = dailyLimit;
      }
      @Override
      public PaymentReceipt pay(Payment payment) {
      reserve(List.of(payment));
      try {
      return inner.pay(payment);
      } catch (RuntimeException ex) {
      release(List.of(payment)); // a failed payment must not eat into the limit
      throw ex;
      }
      }
      @Override
      public List<PaymentReceipt> payAll(List<Payment> batch) {
      reserve(batch);
      try {
      // Whatever inner.payAll calls internally never comes back through this class,
      // so every payment is counted exactly once.
      return inner.payAll(batch);
      } catch (RuntimeException ex) {
      release(batch);
      throw ex;
      }
      }
      // All or nothing: the whole batch fits the limit, or nothing is reserved.
      private synchronized void reserve(List<Payment> payments) {
      Map<String, BigDecimal> after = new HashMap<>();
      for (Payment payment : payments) {
      after.merge(payment.customerNo(), payment.amount(), BigDecimal::add);
      }
      after.replaceAll((customerNo, amount) -> amount.add(usedToday.getOrDefault(customerNo, BigDecimal.ZERO)));
      for (Map.Entry<String, BigDecimal> entry : after.entrySet()) {
      if (entry.getValue().compareTo(dailyLimit) > 0) {
      throw new IllegalStateException("daily limit exceeded for customer " + entry.getKey());
      }
      }
      usedToday.putAll(after);
      }
      private synchronized void release(List<Payment> payments) {
      for (Payment payment : payments) {
      usedToday.merge(payment.customerNo(), payment.amount().negate(), BigDecimal::add);
      }
      }
      }

      src/main/java/com/bank/payment/ PaymentsConfig.java Bağlantı: limit, adaptörün etrafına sarılır. Sıra buradan okunur.

      src/main/java/com/bank/payment/PaymentsConfig.java
      @Configuration
      class PaymentsConfig {
      // Read inside out: the provider, wrapped by the adapter, wrapped by the limit.
      @Bean
      Payments payments(ClearingGateway gateway, @Value("${payments.daily-limit-try}") BigDecimal dailyLimit) {
      return new DailyLimitPayments(new ClearingPayments(gateway), dailyLimit);
      }
      }

      src/main/java/com/bank/payment/ CountingClearingGateway.java Karşı örnek: sağlayıcının sınıfını extends ile genişletmek. Sağlayıcının payAll'u içeride pay çağırır; her ödeme iki kez sayılır.

      src/main/java/com/bank/payment/CountingClearingGateway.java
      // Counter-example: the limit bolted on by extending the vendor's class.
      public class CountingClearingGateway extends ClearingGateway {
      private final DailyUsage usage;
      public CountingClearingGateway(DailyUsage usage) {
      this.usage = usage;
      }
      @Override
      public PaymentReceipt pay(Payment payment) {
      usage.add(payment);
      return super.pay(payment);
      }
      @Override
      public List<PaymentReceipt> payAll(List<Payment> batch) {
      batch.forEach(usage::add);
      // The vendor's payAll calls pay() for each item, which is the override above:
      // every payment in a batch is counted twice. A new SDK version may change that.
      return super.payAll(batch);
      }
      }
      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.

      Hızlı kontrolOrta

      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ı?

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

      Hatalı satıra dokun, sonra kontrol et.

      EmailList.java
      Java 21UTF-8LF

      Kompozisyonun en sık dile getirilen dezavantajı nedir ve nasıl hafifletilir?

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

      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

      Şimşek turu1/5

      Kalıtım, taban sınıfın metotlarının içeride birbirini nasıl çağırdığına da bağlanmak demektir.

      Soru 1/3Orta

      Başkalarının genişletmesi için tasarlamadığın bir public sınıf yazıyorsun. En güvenli varsayılan hangisi?

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

      Aklında kalacak üç şey

      1. 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. 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. 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.
      Sonraki kapı Aynı satır, aynı değişken: çalışınca hangi metodun çalışacağına kim karar veriyor? Polimorfizm ve Dinamik Bağlama — Kararı Kim Veriyor? · 8 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı