Ders 07 / 14
Bileşim Kalıtıma Tercih Edilir
Kırılgan üst sınıf sorununun ölçülmesi: üst sınıfta kendi davranışını değiştirmeyen bir gerçekleştirim değişikliğinin alt sınıfın sayacını 200 nesnenin 200'ünde sessizce bozması, aynı değişikliğin bileşimli sürümde hiçbir sapma üretmemesi ve bileşimin iletme bedelinin sayılması.
İçindekiler
Önceki ders ortak davranışın iki yolla paylaşılabildiğini gösterdi: kalıtımla ve nesneyi argüman alan işlevlerle. Ölçüm o noktada tekrar edilen satır sayısıydı. Aynı tercihin bir başka sonucu daha vardır ve bu, üst sınıf kendi içinde bir değişiklik yaptığında ortaya çıkar. Alt sınıf, üst sınıfın dışarıya söylediklerine değil, içeride ne yaptığına da bağımlı olabilir.
Kırılgan üst sınıf sorunu (fragile base class problem), üst sınıfta yapılan ve üst sınıfın kendi davranışını değiştirmeyen bir gerçekleştirim değişikliğinin alt sınıfların davranışını bozmasıdır. Bu ders sorunu ölçer, sonra aynı değişikliği bileşimli bir sürümde dener.
Sevkiyat ve İki Sarmalama Yolu
Üst sınıf, kapsülleme dersindeki sevkiyat sınıfının sadeleştirilmiş bir sürümüdür: kapasiteyi
aşmayan gönderileri kabul eder, aşanları reddeder. hepsiniEkle yöntemi bir partiyi tek tek
ekle yöntemine verir.
// sevkiyat.mjs — ust sinif, ilk surum: hepsiniEkle her ogeyi kendi ekle yontemiyle isler export class Sevkiyat { #gonderiler = []; #toplam = 0; #kapasite; constructor(kapasiteGram) { this.#kapasite = kapasiteGram; } ekle(gonderi) { if (this.#toplam + gonderi.agirlikGram > this.#kapasite) return false; this.#gonderiler.push(gonderi); this.#toplam += gonderi.agirlikGram; return true; } hepsiniEkle(gonderiler) { for (const g of gonderiler) this.ekle(g); } durum() { return { adet: this.#gonderiler.length, toplam: this.#toplam }; } }
Yeni bir ihtiyaç doğar: kabul edilen gönderilerin sayılması. Birinci karşılık kalıtımdır.
Alt sınıf ekle yöntemini kendi gerçekleştirimiyle karşılar, üst sürümü çağırır ve kabulleri
sayar.
// sayan-kalitim.mjs — kalitimla sarmalama: alt sinif ekle yontemini kendi surumuyle karsilar import { Sevkiyat } from "./sevkiyat.mjs"; export class SayanSevkiyat extends Sevkiyat { #sayac = 0; get sayac() { return this.#sayac; } ekle(gonderi) { const kabul = super.ekle(gonderi); if (kabul) this.#sayac += 1; return kabul; } }
İkinci karşılık bileşimdir. Sarmalayıcı sevkiyatı içeride tutar, dışarıya aynı yöntemleri
sunar ve hepsiniEkle yöntemini kendi ekle yöntemi üzerinden yazar.
// sayan-bilesim.mjs — bilesimle sarmalama: sevkiyat iceride tutulur, yontemler iletilir export class SayanSarmalayici { #ic; #sayac = 0; constructor(sevkiyat) { this.#ic = sevkiyat; } get sayac() { return this.#sayac; } ekle(gonderi) { const kabul = this.#ic.ekle(gonderi); if (kabul) this.#sayac += 1; return kabul; } hepsiniEkle(gonderiler) { for (const g of gonderiler) this.ekle(g); } durum() { return this.#ic.durum(); } }
İki sürüm dışarıdan ayırt edilemez. Koşum, sabit tohumlu bir üreteçle 200 parti üretir, her partiyi iki sarmalayıcıya da verir ve sayaçları bağımsız hesaplanan doğru değerle karşılaştırır.
// kosum.mjs — ayni parti dizisini iki sarmalama yoluna uygular ve sayaclari dogrular import { Sevkiyat } from "./sevkiyat.mjs"; import { SayanSevkiyat } from "./sayan-kalitim.mjs"; import { SayanSarmalayici } from "./sayan-bilesim.mjs"; const uretec = (tohum) => { let x = tohum; return () => (x = (x * 1103515245 + 12345) % 2147483648) / 2147483648; }; const NESNE = 200; const KAPASITE = 20000; const partiler = () => { const rast = uretec(20250729); return Array.from({ length: NESNE }, () => Array.from({ length: 12 }, (_, i) => ({ id: i, agirlikGram: 500 + Math.floor(rast() * 3500) }))); }; const dogruSayi = (parti) => { let toplam = 0, adet = 0; for (const g of parti) if (toplam + g.agirlikGram <= KAPASITE) { toplam += g.agirlikGram; adet += 1; } return adet; }; let kalitimYanlis = 0, bilesimYanlis = 0; const veri = partiler(); for (const parti of veri) { const dogru = dogruSayi(parti); const k = new SayanSevkiyat(KAPASITE); k.hepsiniEkle(parti); const b = new SayanSarmalayici(new Sevkiyat(KAPASITE)); b.hepsiniEkle(parti); if (k.sayac !== dogru) kalitimYanlis += 1; if (b.sayac !== dogru) bilesimYanlis += 1; } const temiz = new Sevkiyat(KAPASITE); temiz.hepsiniEkle(veri[0]); const d = temiz.durum(); console.log(`ust sinifin kendi davranisi = adet ${d.adet}, toplam ${d.toplam}`); console.log(`kalitimli sayac yanlis = ${kalitimYanlis}/${NESNE}`); console.log(`bilesimli sayac yanlis = ${bilesimYanlis}/${NESNE}`); const yeni = (n) => (typeof n.muhurle === "function" ? "var" : "yok"); console.log(`yeni ust sinif yontemi = kalitim ${yeni(new SayanSevkiyat(KAPASITE))},` + ` bilesim ${yeni(new SayanSarmalayici(new Sevkiyat(KAPASITE)))}`);
node kosum.mjs
ust sinifin kendi davranisi = adet 9, toplam 18566 kalitimli sayac yanlis = 0/200 bilesimli sayac yanlis = 0/200 yeni ust sinif yontemi = kalitim yok, bilesim yok
İki yol da doğru sayıyor. Bu noktada aralarında seçim yapmak için bir gerekçe görünmüyor.
Üst Sınıfta Bir Değişiklik
Üst sınıfın bakımını üstlenen taraf hepsiniEkle yöntemini yeniden yazar: her öge için
yöntem çağrısı yapmak yerine döngüyü kendi içinde çalıştırır. Aynı sürümde bir de yeni bir
yöntem eklenir — sevkiyatın kapasitesini o anki toplama sabitleyen muhurle.
// sevkiyat-v2.mjs — ust sinifin ikinci surumu: hepsiniEkle ic dongusunu kendi icinde yapar export class Sevkiyat { #gonderiler = []; #toplam = 0; #kapasite; constructor(kapasiteGram) { this.#kapasite = kapasiteGram; } ekle(gonderi) { if (this.#toplam + gonderi.agirlikGram > this.#kapasite) return false; this.#gonderiler.push(gonderi); this.#toplam += gonderi.agirlikGram; return true; } hepsiniEkle(gonderiler) { for (const g of gonderiler) { if (this.#toplam + g.agirlikGram > this.#kapasite) continue; this.#gonderiler.push(g); this.#toplam += g.agirlikGram; } } muhurle() { this.#kapasite = this.#toplam; } durum() { return { adet: this.#gonderiler.length, toplam: this.#toplam }; } }
Değişiklik üst sınıfın kendi davranışını korur: aynı gönderiler kabul edilir, aynı gönderiler reddedilir, aynı toplam çıkar. Aynı koşum yeni sürümle tekrarlanır.
cp sevkiyat-v2.mjs sevkiyat.mjs
node kosum.mjs
ust sinifin kendi davranisi = adet 9, toplam 18566 kalitimli sayac yanlis = 200/200 bilesimli sayac yanlis = 0/200 yeni ust sinif yontemi = kalitim var, bilesim yok
Üst sınıfın kendi davranışı birinci satırda birebir aynı: 9 gönderi, 18566 gram. Üst sınıf için yazılmış her sınama iki sürümde de geçerdi. Alt sınıfın sayacı ise 200 nesnenin 200’ünde yanlışa döndü. Hiçbir istisna fırlamadı, hiçbir uyarı çıkmadı; sayaç sıfırda kaldı.
Sapmanın Kaynağı
Alt sınıfın sayacı, üst sınıfın hepsiniEkle yöntemi içinde this.ekle çağırmasına
bağlıydı. Bu çağrı üst sınıfın belgelenmiş sözleşmesinin parçası değildi; bir gerçekleştirim
ayrıntısıydı. Alt sınıf o ayrıntıya, farkında olmadan, bir bağımlılık kurdu.
Buradaki asıl gözlem şudur: kalıtım, üst sınıfın iç çağrı örüntüsünü de sözleşmenin parçası hâline getirir. Bir üst sınıfın alt sınıflara verdiği güvence yalnız “hangi yöntemler var, ne döndürürler” değildir; “hangi yöntem hangisini çağırır” da güvencenin içine girer. Bu ikinci bölüm neredeyse hiçbir zaman yazılmaz ve yazılmadığı için de korunmaz.
Bileşimli sarmalayıcı bu bağımlılığı hiç kurmadı. Onun hepsiniEkle yöntemi kendi ekle
yöntemini çağırıyordu; iç nesnenin hepsiniEkle yöntemini hiç kullanmadı. İç nesneden
istediği tek şey, açıkça çağırdığı ekle ve durum yöntemlerinin davranışıydı — yani
belgelenmiş sözleşme. Üst sınıfın iç düzenlemesi değiştiğinde etkilenmemesinin nedeni budur.
Bileşimin Bedeli
Son satır bileşimin bedelini gösteriyor. Yeni sürümde eklenen muhurle yöntemi kalıtımlı
sürümde kendiliğinden ortaya çıktı, bileşimli sürümde çıkmadı. Sarmalayıcı, iç nesnenin
kazandığı hiçbir yeteneği bedava almaz.
Bedelin ikinci yüzü iletme kodudur. Sarmalayıcı üç yöntemi elle yazdı: ekle, hepsiniEkle
ve durum. İç sınıfın arayüzü on yönteme çıksaydı, sarmalayıcının dışarıya aynı yüzü
sunabilmesi için on iletme yazılması gerekirdi. Kalıtımda bu sayı sıfırdır.
İki bedel karşılaştırılabilir. Kalıtımın bedeli, üst sınıf her değiştiğinde ortaya çıkabilen ve ölçümde 200/200 olarak görünen sessiz sapmadır. Bileşimin bedeli, arayüz her genişlediğinde yazılması gereken ve derhâl görünen iletme kodudur. İkincisi sayılabilir ve öngörülebilir; birincisi ne sayılabilir ne öngörülebilir. Tercihin gerekçesi budur.
Kalıtımın Ucuz Kaldığı Durum
Kalıtım her durumda pahalı değildir. Üst sınıf ile alt sınıf aynı ekip tarafından ve aynı sürümle bakılıyorsa, üst sınıfın iç çağrı örüntüsü değiştiğinde alt sınıflar da gözden geçirilebilir; sapma sessiz kalmaz. Aynı biçimde, üst sınıf değişmeye kapalıysa — sürümü sabitlenmiş, davranışı dondurulmuş bir tipse — kırılganlık zemini ortadan kalkar.
Kırılganlık, üst sınıfın alt sınıflardan bağımsız olarak değiştiği durumlarda doğar: farklı bir ekip, farklı bir sürüm hattı, farklı bir depo. Ölçüt sınıf sayısı ya da hiyerarşi derinliği değil, kimin ne zaman değiştirdiğidir.
Özet
- Kırılgan üst sınıf sorunu, üst sınıfın kendi davranışını korumasına rağmen alt sınıfları bozan bir gerçekleştirim değişikliğidir.
- Ölçümde üst sınıfın kendi davranışı iki sürümde birebir aynı kaldı (9 gönderi, 18566 gram); buna rağmen kalıtımlı sayaç 200 nesnenin 200’ünde yanlışa döndü.
- Aynı değişiklik bileşimli sarmalayıcıda 0 sapma üretti; sarmalayıcı iç nesneden yalnız açıkça çağırdığı yöntemlerin davranışına bağımlıydı.
- Kalıtım, üst sınıfın iç çağrı örüntüsünü de sözleşmenin parçası yapar; bu bölüm neredeyse hiç yazılmadığı için korunmaz.
- Bileşimin bedeli iletme kodudur ve sayılabilir: sarmalayıcı burada üç yöntem iletti; ayrıca üst sınıfa eklenen yeni yöntem bileşimli sürümde kendiliğinden görünmedi.
- Kalıtım, üst sınıfla alt sınıfın birlikte bakıldığı ya da üst sınıfın değişime kapalı olduğu durumlarda ucuz kalır; ölçüt kimin ne zaman değiştirdiğidir.
Sonraki Adım
Bu ders iki nesnenin birbirine nasıl bağlanacağını ölçtü. Bir sonraki soru bir nesnenin kendi içinde ne taşıyacağıdır. Sevkiyat sınıfı hem gönderileri hem kapasite kuralını tutuyordu; kural veriyle aynı yerdeydi. Yaygın bir başka düzen bunu ayırır: veri yalnız alan taşır, kurallar ayrı işlemci birimlerine dağılır. Sonraki ders iki düzeni karşılaştırır ve bir kuralın kaç ayrı yerde tekrarlandığını sayar; sonra kural değiştirildiğinde her iki düzende kaç sonucun birbirinden ayrıldığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.