Ders 04 / 14
Kalıtım
Kalıtımın iki ayrı vaadinin ayrılması: gerçekleştirim paylaşımı satır sayısıyla, alt tip ilişkisi ise üç maddelik bir ücret sözleşmesiyle ölçülür; sözleşmeye uyan ve uymayan iki alt sınıf aynı istemciyle koşturulup uymayan sürümün ürettiği yanlış sonuç sayılır.
İçindekiler
Önceki ders tarife modülünün temsilini gizledi ve istemcilerin bildiği ayrıntıyı sıfıra indirdi. O modül tek bir tarife biçimini gizliyordu. Gerçek bir ücretlendirme kitaplığında tarife türü birden fazladır: sigortalı gönderi tarifesi, elden teslim tarifesi, sözleşmeli müşteri tarifesi. Aynı adları farklı gerçekleştirimlerle karşılamanın yollarından biri bir türü bir başkasından türetmektir.
Kalıtım (inheritance), bir sınıfın bir başkasının alanlarını ve yöntemlerini devralması ve gerektiğinde bunların bir bölümünü kendi gerçekleştirimiyle değiştirmesidir. Programlama Temelleri kursunda tanıtılan tanım budur. Bu derste sorulan soru şudur: türetme her zaman bir alt tip (subtype) üretir mi, üretmediğinde bunu ne gösterir ve bedeli nedir.
Kalıtımın İki Ayrı Vaadi
Kalıtım tek bir araçmış gibi anılır, ama iki ayrı şey vaat eder ve bu ikisi birbirini gerektirmez.
Birincisi gerçekleştirim paylaşımı: alt sınıf, üst sınıfın kodunu tekrar yazmaz. Bu vaat sayılabilir ve neredeyse her zaman tutulur.
İkincisi alt tip ilişkisi: üst tipi bekleyen her yere alt tip konabilir ve program doğru
çalışmaya devam eder. Bu vaat kodun biçimiyle değil, davranışıyla ilgilidir ve dil onu
denetlemez. extends yazmak birinciyi kesin olarak sağlar; ikinciyi hiç sağlamayabilir.
İkisini ayırmak için üst tipin ne söz verdiği yazılı olmalıdır. Bir üst tipin sözleşmesi, yöntemlerinin girdileri ve sonuçları hakkında verdiği güvencelerdir. Alt tip bu güvenceleri zayıflatamaz: girdi kümesini daraltamaz, sonuç hakkında verilen sözü bozamaz, üst tipin değişmezini çiğneyemez. Bu kural Liskov yerine geçme ilkesi adıyla bilinir.
Üst Tip ve Ücret Sözleşmesi
Kademeli tarife bir sınıfa dönüşür. Kademeler, katsayılar ve eşikler özeldir; dışarıya
ucret, bolgeler ve asgari açılır. taban yöntemi alt sınıfların kullanımına açıktır.
// tarife.mjs — ust tip: kademeli tarife; ucret sozlesmesini bu sinif tanimlar export class Tarife { #kademeler; #kiloBasiEk; #katsayi; #asgari; constructor({ kademeler, kiloBasiEk, katsayi, asgari }) { this.#kademeler = kademeler; this.#kiloBasiEk = kiloBasiEk; this.#katsayi = katsayi; this.#asgari = asgari; } get asgari() { return this.#asgari; } bolgeler() { return Object.keys(this.#katsayi).map(Number); } taban(agirlik) { const kademe = this.#kademeler.find(([ust]) => agirlik <= ust); if (kademe !== undefined) return kademe[1]; const [sonUst, sonUcret] = this.#kademeler.at(-1); return sonUcret + Math.ceil((agirlik - sonUst) / 1000) * this.#kiloBasiEk; } ucret(agirlik, bolge) { return Math.max(Math.round(this.taban(agirlik) * this.#katsayi[bolge]), this.#asgari); } } export const AYAR = { kademeler: [[1000, 4500], [5000, 7000], [10000, 11000]], kiloBasiEk: 1800, katsayi: { 1: 1, 2: 1.25, 3: 1.6 }, asgari: 5000, };
ucret yönteminin sözleşmesi üç maddedir:
- S1 — Her pozitif ağırlık ve
bolgeler()listesindeki her bölge için tanımlıdır; sonlu bir sayı döndürür. - S2 — Sonuç asgari ücretin altına inmez.
- S3 — Sonuç ağırlıkla azalmaz:
a1 <= a2iseucret(a1) <= ucret(a2).
Üçüncü madde koddan okunamaz; kademelerin artan olması ve katsayının pozitifliğinden çıkar. Yazılı olmadığı sürece alt sınıf yazan kişinin bunu bilmesi için bir neden yoktur. Sözleşmeyi yazmak, kalıtımın ikinci vaadini denetlenebilir kılmanın tek yoludur.
Sözleşmeye Uyan ve Uymayan İki Alt Sınıf
İlk alt sınıf sigortalı gönderi tarifesidir: ağırlıkla artan bir sigorta payını üst sınıfın sonucuna ekler.
// sigortali.mjs — sozlesmeye uyan alt sinif: agirlikla artan bir sigorta payi ekler import { Tarife } from "./tarife.mjs"; export class SigortaliTarife extends Tarife { #gramBasi; constructor(ayar, gramBasi) { super(ayar); this.#gramBasi = gramBasi; } ucret(agirlik, bolge) { return super.ucret(agirlik, bolge) + Math.round(agirlik * this.#gramBasi); } }
İkincisi elden teslim tarifesidir. Talep gerçektir: 6 ile 9 kilogram arasındaki gönderiler tek kişiyle taşınamadığı için ek ücretlendirilir. Gerçekleştirim de doğrudan görünür.
// elden-teslim.mjs — sozlesmeyi ciyneyen alt sinif: bir agirlik bandina ek ucret koyar import { Tarife } from "./tarife.mjs"; export class EldenTeslimTarifesi extends Tarife { #bandaEk; constructor(ayar, bandaEk) { super(ayar); this.#bandaEk = bandaEk; } ucret(agirlik, bolge) { const taban = super.ucret(agirlik, bolge); return agirlik >= 6000 && agirlik <= 9000 ? taban + this.#bandaEk : taban; } }
İki sınıf da aynı biçimde yazılmıştır: aynı üst sınıf, aynı yöntem imzası, aynı dönüş tipi. Aralarındaki fark bandın çıkışındadır: 9000 gramda ücret ek yükle birlikte yüksektir, 9001 gramda ek kalkar ve ücret düşer. S3 maddesi çiğnenmiştir.
Gerçekleştirim paylaşımı vaadi ikisinde de tutuldu. Ölçüsü satır sayısıdır.
for f in tarife.mjs sigortali.mjs elden-teslim.mjs; do echo "$f $(grep -c . $f) satir"; done
tarife.mjs 27 satir sigortali.mjs 9 satir elden-teslim.mjs 10 satir
Yirmi yedi satırlık kademe, katsayı ve asgari ücret mantığı iki alt sınıfın hiçbirinde tekrar edilmedi; ikisi de dokuz–on satırla yetindi. Kalıtımın birinci vaadi bakımından iki alt sınıf arasında hiçbir fark yok. Fark yalnız ikinci vaatte.
Sözleşme Denetleyicisi
Üç madde bir ağırlık ızgarası üzerinde denenir. Denetleyici üst tipe bağımlıdır; hangi alt sınıfın verildiğini bilmez.
// sozlesme.mjs — ucret sozlesmesinin uc maddesini bir agirlik izgarasinda dener export function ihlaller(tarife) { const kirik = new Set(); for (const bolge of tarife.bolgeler()) { let onceki = -Infinity; for (let a = 100; a <= 20000; a += 100) { let u; try { u = tarife.ucret(a, bolge); } catch { kirik.add("S1 her gecerli girdide tanimli"); continue; } if (!Number.isFinite(u)) kirik.add("S1 her gecerli girdide tanimli"); if (u < tarife.asgari) kirik.add("S2 asgari ucretin altina inmez"); if (u < onceki) kirik.add("S3 agirlikla azalmaz"); onceki = u; } } return [...kirik].sort(); }
Üst Tip Üzerinden Çalışan İstemci
İstemci, verilen bütçeyle gönderilebilecek en ağır gönderiyi bulur. İkili arama kullanır ve bu seçim doğrudan S3 maddesine dayanır: bir ağırlıkta ücret bütçeyi aşıyorsa, daha ağır hiçbir gönderi bütçeye girmez. Aynı dosya, doğruluk ölçütü olarak tek tek tarayan yavaş bir sürümü de içerir.
// butce.mjs — istemci: verilen butceyle gonderilebilecek en agir gonderiyi bulur const UST = 16000; export function ikiliArama(tarife, bolge, butce) { let alt = 0, ust = UST; while (alt < ust) { const orta = Math.ceil((alt + ust) / 2); if (tarife.ucret(orta, bolge) <= butce) alt = orta; else ust = orta - 1; } return alt; } export function tarama(tarife, bolge, butce) { let en = 0; for (let a = 0; a <= UST; a += 1) if (tarife.ucret(a, bolge) <= butce) en = a; return en; }
Koşum, iki alt sınıfı önce sözleşme denetiminden, sonra üç bölge ve altmış bütçe üzerinde istemciden geçirir.
// kosum.mjs — iki alt sinifi ayni sozlesme denetiminden ve ayni istemciden gecirir import { AYAR } from "./tarife.mjs"; import { SigortaliTarife } from "./sigortali.mjs"; import { EldenTeslimTarifesi } from "./elden-teslim.mjs"; import { ihlaller } from "./sozlesme.mjs"; import { ikiliArama, tarama } from "./butce.mjs"; const BUTCELER = Array.from({ length: 60 }, (_, i) => 5000 + i * 500); for (const [ad, tarife] of [ ["SigortaliTarife", new SigortaliTarife(AYAR, 0.2)], ["EldenTeslimTarifesi", new EldenTeslimTarifesi(AYAR, 8000)], ]) { const kirik = ihlaller(tarife); let yanlis = 0; let enBuyukFark = 0; for (const bolge of tarife.bolgeler()) { for (const butce of BUTCELER) { const bulunan = ikiliArama(tarife, bolge, butce); const dogru = tarama(tarife, bolge, butce); if (bulunan !== dogru) { yanlis += 1; enBuyukFark = Math.max(enBuyukFark, dogru - bulunan); } } } console.log(`${ad}`); console.log(` sozlesme ihlali = ${kirik.length} ${kirik.join("; ")}`); console.log(` yanlis sonuc = ${yanlis}/${BUTCELER.length * 3}`); console.log(` en buyuk sapma = ${enBuyukFark} gram`); }
node kosum.mjs
SigortaliTarife sozlesme ihlali = 0 yanlis sonuc = 0/180 en buyuk sapma = 0 gram EldenTeslimTarifesi sozlesme ihlali = 1 S3 agirlikla azalmaz yanlis sonuc = 48/180 en buyuk sapma = 8001 gram
Elden teslim tarifesiyle 180 sorgunun 48’i yanlış yanıtlandı. En büyük sapmada istemci, bütçenin gerçekte 8001 gram daha ağır bir gönderiyi karşıladığı durumda daha hafif bir gönderi önerdi. İstemcinin kodunda hata yok; ikili arama S3 maddesi geçerliyken doğrudur. Hata, sözleşmeyi çiğneyen alt sınıfın üst tip yerine konmasıyla ortaya çıktı.
Sayının 180’in tamamı olmaması da bilgi taşıyor. İhlal, ancak aramanın bir yoklaması ek ücret bandına düştüğünde görünür oluyor; diğer sorgularda program doğru yanıt veriyor. Sözleşme ihlalleri her çağrıda değil, belirli girdi bölgelerinde patlar; bu yüzden örnek girdilerle yapılan sınamalarda gözden kaçar.
Sözleşme Nerede Yazılı
Elden teslim tarifesi hiçbir tip denetiminden geçmez değildir; tam tersine her denetimden
geçer. Yöntem imzası aynı, dönüş tipi aynı, extends bağı geçerli. Statik tipleme S1’in bir
bölümünü — parametre ve dönüş tiplerini — yakalar; S2 ve S3’ü yakalamaz, çünkü bunlar
tiplerin değil değerlerin ilişkisidir.
Ölçülen 48 yanlış sonuç, sözleşmenin yazılı olmamasının bedelidir. Üç maddenin metne
dökülmesi ve sozlesme.mjs gibi bir denetleyiciyle her alt sınıfa uygulanması, bu bedeli
istemcinin çalışma zamanından alt sınıfın yazıldığı ana taşır. Aynı denetleyici ihlali
tek satırda gösterdi.
Elden teslim ihtiyacının kendisi yanlış değildi; onu bir alt sınıf olarak modellemek yanlıştı. Bant ücreti tarifenin bir alt türü değil, tarifeye eklenen ayrı bir hizmet kalemidir; ücretin yanında ayrı bir alan olarak taşınırsa S3 çiğnenmez.
Özet
- Kalıtım iki ayrı şey vaat eder: gerçekleştirim paylaşımı ve alt tip ilişkisi;
extendsbirincisini kesin olarak sağlar, ikincisini hiç sağlamayabilir. - Gerçekleştirim paylaşımı iki alt sınıfta da tutuldu: 27 satırlık üst sınıf mantığı dokuz ve on satırlık alt sınıflarda tekrar edilmedi.
- Alt tip ilişkisi ancak üst tipin sözleşmesi yazılıysa denetlenebilir; buradaki sözleşme üç maddeydi ve üçüncüsü koddan okunamıyordu.
- Sözleşmeye uyan alt sınıf 0 ihlal ve 180 sorguda 0 yanlış sonuç verdi; uymayan alt sınıf 1 ihlal ve 48 yanlış sonuç verdi, en büyük sapma 8001 gramdı.
- İhlal her çağrıda değil belirli girdi bölgelerinde görünür; örnek girdilerle yapılan sınamada 180 sorgunun 132’si doğru yanıt verdi.
- Statik tipleme yalnız imza uyumunu denetler; sonucun değerine ilişkin maddeler yazılı bir sözleşme ve onu uygulayan bir denetleyici gerektirir.
Sonraki Adım
Bu derste iki alt sınıf da aynı adla — ucret — farklı davrandı ve istemci hangisiyle
çalıştığını bilmedi. Bu, tek bir adın birden çok davranışa karşılık gelmesidir ve alt tip
ilişkisi bunun yalnız bir yoludur. Sonraki ders üç yolu da kurar: alt tip, parametrik ve
geçici çok biçimlilik. Ölçüm de değişir — aynı problem üç yolla gerçekleştirilir, sonra
sisteme yeni bir tarife türü ve yeni bir işlem eklenir ve her yolda dokunulan dosya sayısı
sayılır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.