Ders 04 / 19
Değer Nesneleri
Değerle tanımlanan değişmez nesnelerin seçim ölçütü: gram cinsinden gelen on iki girdinin çıplak sayı modelinde kaç yanlış tutar ürettiğinin sayılması, eşitlik kuralının kaç satırda elle yazıldığının ölçülmesi, paylaşılan bir ölçünün yerinde düzeltilmesiyle kaç gönderinin bozulduğunun çıkarılması ve iki modelin aynı gruplamayı verdiğinin gösterilmesi.
İçindekiler
Önceki dersin son bölümü kimlik gerektirmeyen kavramları saydı ama onları hâlâ çıplak sayı olarak tuttu: ağırlık bir sayıydı, bölge bir katardı. O zaman kilogramla gramı aynı parametreye geçirmek, ikisini toplamak ve karşılaştırmak kodun izin verdiği işlemler olur.
Kimliği olmayan, eşitliği taşıdığı değerle tanımlanan ve bir kez kurulduktan sonra değişmeyen nesneye değer nesnesi (value object) denir. Tanım Nesneye Dayalı Programlamaya Giriş kursunda verilmişti. Buradaki iş yine seçim ölçütüdür ve üç soruya ayrılır: kimlik gerekiyor mu, eşitlik neye göre kurulacak, değişmezlik neyi ucuzlatıyor.
Ölçüt
Önceki dersin iki sorusu bir kavram için de hayır yanıtı veriyorsa o kavram varlık nesnesi değildir: iki tanesi arasında ayrım yoktur ve bir yaşam öyküsü taşımaz. Ağırlık böyledir. 2,4 kilogram ile bir başka 2,4 kilogram aynı şeydir; ağırlığın “düzeltilmesi” ağırlığın değişmesi değil, gönderinin başka bir ağırlığa sahip olmasıdır.
İkinci soru eşitliğin neye göre kurulacağıdır ve yanıtı ölçünün birimini de içine alır. 2400 gram ile 2,4 kilogram eşittir; 2400 ile 2,4 eşit değildir. Aradaki fark, birimin nerede tutulduğudur.
Çıplak Sayı Modeli
Birinci sürümde birim yalnız parametre adında yazılıdır.
mkdir -p ciplak deger
// ciplak/ucret.mjs — agirlik ciplak sayi, birim yalnizca parametre adinda const KADEMELER = [[1, 3900], [5, 6400], [Infinity, 11800]]; const BOLGE_KATSAYISI = { "34": 1, "06": 1.35, "65": 1.8 }; const ASGARI_UCRET = 3990; export function netUcret(agirlikKg, bolge, oran) { const taban = KADEMELER.find(([ust]) => agirlikKg <= ust)[1]; const ham = taban * (BOLGE_KATSAYISI[bolge] ?? 1.8); return Math.max(Math.round(ham * (1 - oran)), ASGARI_UCRET); }
// ciplak/mutabakat.mjs — esitlik kurali her cagri yerinde yeniden yazilir export const tartimUyuyorMu = (beyanKg, tartimKg) => Math.abs(beyanKg - tartimKg) < 0.001; export const ayniKademeMi = (aKg, bKg) => Math.abs(aKg - bKg) < 0.001; export const agirlikTopla = (aKg, bKg) => aKg + bKg;
// ciplak/rapor.mjs — agirliga gore gruplama; esitlik kurali burada da yazili export function agirlikGruplari(agirliklarKg) { const gruplar = []; for (const a of agirliklarKg) { const grup = gruplar.find((g) => Math.abs(g[0] - a) < 0.001); if (grup) grup.push(a); else gruplar.push([a]); } return gruplar; }
Kayan noktalı sayıların karşılaştırılması bir hoşgörü payı gerektirir ve o pay üç dosyada üç kez yazılmış durumda. Payın büyüklüğü bir alan kararıdır — kaç grama kadar iki ağırlık aynı sayılır — ama kodda alan kararı gibi görünmüyor.
Değer Nesnesi Modeli
İkinci sürümde ağırlık kendi tipini alır. Birim yapıcıda çözülür, iç gösterim gram olur, nesne dondurulur.
// deger/agirlik.mjs — degistirilemez deger nesnesi: birim yapinin icinde const GRAM = { kg: 1000, g: 1 }; export class Agirlik { constructor(miktar, birim) { if (!(birim in GRAM)) throw new RangeError(`bilinmeyen agirlik birimi: ${birim}`); this.gram = Math.round(miktar * GRAM[birim]); Object.freeze(this); } static kg(miktar) { return new Agirlik(miktar, "kg"); } static g(miktar) { return new Agirlik(miktar, "g"); } kilogram() { return this.gram / 1000; } esitMi(o) { return o instanceof Agirlik && o.gram === this.gram; } arti(o) { return new Agirlik(this.gram + o.gram, "g"); } }
Hoşgörü payı ortadan kalktı çünkü iç gösterim tam sayıdır: yuvarlama bir kez yapıcıda
yapılıyor, karşılaştırma tam sayı eşitliği oluyor. arti yeni bir nesne döndürüyor, çağıranı
değiştirmiyor.
// deger/ucret.mjs — agirlik yerine Agirlik bekler, ciplak sayiyi reddeder import { Agirlik } from "./agirlik.mjs"; const KADEMELER = [[1, 3900], [5, 6400], [Infinity, 11800]]; const BOLGE_KATSAYISI = { "34": 1, "06": 1.35, "65": 1.8 }; const ASGARI_UCRET = 3990; export function netUcret(agirlik, bolge, oran) { if (!(agirlik instanceof Agirlik)) throw new TypeError("agirlik bir Agirlik olmali"); const taban = KADEMELER.find(([ust]) => agirlik.kilogram() <= ust)[1]; const ham = taban * (BOLGE_KATSAYISI[bolge] ?? 1.8); return Math.max(Math.round(ham * (1 - oran)), ASGARI_UCRET); }
// deger/mutabakat.mjs — esitlik kurali deger nesnesinin icinde, cagri yeri onu cagirir export const tartimUyuyorMu = (beyan, tartim) => beyan.esitMi(tartim); export const ayniKademeMi = (a, b) => a.esitMi(b); export const agirlikTopla = (a, b) => a.arti(b);
// deger/rapor.mjs — ayni gruplama; esitlik deger nesnesinin kendi yontemi export function agirlikGruplari(agirliklar) { const gruplar = []; for (const a of agirliklar) { const grup = gruplar.find((g) => g[0].esitMi(a)); if (grup) grup.push(a); else gruplar.push([a]); } return gruplar; }
Ölçüm
Üç ölçü alınır. Yirmi dört girdinin yarısı gram cinsinden gelir; kaç tanesinde sessizce yanlış tutar üretildiği sayılır. Eşitlik kuralının kaç satırda yazıldığı taranır. Beş gönderi aynı ölçüyü paylaşırken biri düzeltildiğinde kaç gönderinin bozulduğu ölçülür.
// deger-olc.mjs — birim karismasi, esitlik kuralinin yeri ve paylasilan ornegin bozulmasi import { readFileSync } from "node:fs"; import { netUcret as ciplakUcret } from "./ciplak/ucret.mjs"; import { netUcret as degerUcret } from "./deger/ucret.mjs"; import { Agirlik } from "./deger/agirlik.mjs"; import { agirlikGruplari as ciplakGrup } from "./ciplak/rapor.mjs"; import { agirlikGruplari as degerGrup } from "./deger/rapor.mjs"; const BOLGELER = ["34", "06", "65"]; const GIRDILER = []; for (let i = 0; i < 24; i += 1) { const kg = [0.4, 0.9, 2.4, 3.1, 7, 12][i % 6]; GIRDILER.push({ kaynak: i % 2 === 0 ? "kg" : "g", kg, bolge: BOLGELER[i % 3] }); } // dogru tutar: kural, gercek kilogram degerine uygulanmis hali const dogruUcret = (g) => ciplakUcret(g.kg, g.bolge, 0); let ciplakYanlis = 0; for (const g of GIRDILER) { const sayi = g.kaynak === "kg" ? g.kg : g.kg * 1000; if (ciplakUcret(sayi, g.bolge, 0) !== dogruUcret(g)) ciplakYanlis += 1; } let degerYanlis = 0; let degerHata = 0; for (const g of GIRDILER) { const sayi = g.kaynak === "kg" ? g.kg : g.kg * 1000; if (degerUcret(new Agirlik(sayi, g.kaynak), g.bolge, 0) !== dogruUcret(g)) degerYanlis += 1; try { degerUcret(sayi, g.bolge, 0); } catch { degerHata += 1; } } console.log(`24 girdinin 12'si gram cinsinden geliyor`); console.log(` ciplak: sessizce yanlis ucret ${ciplakYanlis}/24, firlatilan hata 0/24`); console.log(` deger : sessizce yanlis ucret ${degerYanlis}/24, ` + `ciplak sayi gecirildiginde hata ${degerHata}/24`); const KURAL = /Math\.abs\(|\.gram ===/; for (const [ad, dosyalar] of Object.entries({ ciplak: ["ciplak/ucret.mjs", "ciplak/mutabakat.mjs", "ciplak/rapor.mjs"], deger: ["deger/ucret.mjs", "deger/mutabakat.mjs", "deger/rapor.mjs", "deger/agirlik.mjs"], })) { const satir = dosyalar.flatMap((d) => readFileSync(d, "utf8").split("\n")); console.log(`${ad}: esitlik kurali yazan satir ${satir.filter((s) => KURAL.test(s)).length}`); } const OLCUM = [0.4, 2.4, 0.4, 7, 2.4, 12, 7, 0.9]; const cGrup = ciplakGrup(OLCUM).map((g) => g.length).join(","); const dGrup = degerGrup(OLCUM.map((k) => Agirlik.kg(k))).map((g) => g.length).join(","); console.log(`8 olcumun gruplanmasi -> ciplak [${cGrup}], deger [${dGrup}], ` + `ayni mi ${cGrup === dGrup}`); const paylasilan = { miktarKg: 2.4 }; const gonderiler = [0, 1, 2, 3, 4].map(() => ({ agirlik: paylasilan })); paylasilan.miktarKg = 3.1; const bozulan = gonderiler.slice(1).filter((g) => g.agirlik.miktarKg !== 2.4).length; const ilkAgirlik = Agirlik.kg(2.4); const gonderilerD = [0, 1, 2, 3, 4].map(() => ({ agirlik: ilkAgirlik })); gonderilerD[0].agirlik = Agirlik.kg(3.1); const bozulanD = gonderilerD.slice(1).filter((g) => !g.agirlik.esitMi(Agirlik.kg(2.4))).length; let yerindeHata = 0; try { ilkAgirlik.gram = 3100; } catch { yerindeHata = 1; } console.log(`5 gonderi ayni agirlik ornegini paylasiyor, biri duzeltiliyor`); console.log(` degistirilebilir kayit: bozulan gonderi ${bozulan}/4`); console.log(` deger nesnesi : bozulan gonderi ${bozulanD}/4, ` + `yerinde degistirme denemesi hata ${yerindeHata}`);
node deger-olc.mjs
24 girdinin 12'si gram cinsinden geliyor ciplak: sessizce yanlis ucret 8/24, firlatilan hata 0/24 deger : sessizce yanlis ucret 0/24, ciplak sayi gecirildiginde hata 24/24 ciplak: esitlik kurali yazan satir 3 deger: esitlik kurali yazan satir 1 8 olcumun gruplanmasi -> ciplak [2,2,2,1,1], deger [2,2,2,1,1], ayni mi true 5 gonderi ayni agirlik ornegini paylasiyor, biri duzeltiliyor degistirilebilir kayit: bozulan gonderi 4/4 deger nesnesi : bozulan gonderi 0/4, yerinde degistirme denemesi hata 1
Birinci ölçü birim sorusunu yanıtlıyor. Gram cinsinden gelen on iki girdinin sekizinde çıplak sayı modeli yanlış tutar üretiyor ve bunu sessizce yapıyor: hata sayısı sıfırdır, çağıran taraf hiçbir uyarı almaz. Kalan dört girdide tutar tesadüfen doğru çıkıyor, çünkü 7 ve 12 kilogramlık gönderiler gram olarak da en üst kademeye düşüyor; tesadüf bir güvence değildir. Değer nesnesi modelinde yanlış tutar sayısı sıfır. Aynı modelde çıplak bir sayı geçirildiğinde yirmi dört çağrının yirmi dördü hata fırlatıyor — sessiz yanlış, gürültülü hataya dönüşmüş durumda.
İkinci ölçü eşitlik sorusunu yanıtlıyor. Hoşgörü payı çıplak modelde üç satırda yazılı; değer
nesnesi modelinde bir satırda, esitMi yönteminin içinde. Payın alan kararı olduğu ikinci
durumda görünür hâle geliyor: değişmesi gerekirse dokunulacak yer birdir.
Üçüncü ölçü değişmezliğin ne ucuzlattığını gösteriyor. Değiştirilebilir kayıt beş gönderi tarafından paylaşıldığında bir düzeltme dördünü bozuyor. Değer nesnesi modelinde aynı düzeltme yeni bir nesne üretiyor, dolayısıyla bozulan gönderi sayısı sıfır. Dondurulmuş nesnede yerinde değiştirme denemesi hata veriyor. Kazanç bir savunma kopyası yazmak zorunda kalmamaktır: paylaşım bedelsiz olur.
Gruplama satırı yine hizanın doğruluktan ayrı olduğunu söylüyor: sekiz ölçüm iki modelde de aynı beş gruba dağılıyor.
Değer Nesnesi Neyi Taşıyabilir
Değer nesnesinin taşıdığı asıl kazanç kendi kurallarını taşıyabilmesidir. Ağırlık negatif olamaz, bölge kodu iki basamaklıdır, indirim oranı sıfırla bir arasındadır. Bu kurallar çıplak sayı modelinde çağrı yerlerine dağılır; değer nesnesinde yapıcıya girer ve bir kez yazılır. Bunun sonucu, kuralı bozan bir değerin hiç var olamamasıdır — sonraki dersin ölçtüğü şey tam olarak bu tür ulaşılabilirliktir.
Kitaplıkta aynı işlemi gören ikinci aday para tutarıdır: kuruş ile lira karışması ağırlıkla aynı hatayı üretir ve çözümü de aynıdır — tutarı kuruş cinsinden tam sayı olarak tutan, eşitliği ve toplamayı kendi üzerinde taşıyan bir tip.
Ne Zaman Karşılığını Vermez
Bedel iki kalemdir: her kavram için bir tip ve her erişim için bir yöntem çağrısı. Ölçüm dosyalarında bu bedel görünüyor — değer nesnesi sürümü dört dosya tutuyor, çıplak sürüm üç.
Kavramın tek bir yerde okunup bir daha kullanılmadığı durumlarda bu bedel karşılıksız kalır. Bir sınırdan geçen ham kayıtta ağırlık bir sayıdır; onu tipe çevirmenin kazancı ancak o değer kitaplığın içinde dolaşmaya başladığında doğar. Aynı biçimde birimi tek olan ve hiç karşılaştırılmayan bir ölçü — kütüğe yazılan bir sıra numarası gibi — tip açmayı hak etmez.
Özet
- Değer nesnesi kimliği olmayan, eşitliği taşıdığı değerle kurulan ve kurulduktan sonra değişmeyen nesnedir; ölçütü üç sorudur: kimlik, eşitlik, değişmezlik.
- Gram cinsinden gelen on iki girdinin 8’inde çıplak sayı modeli sessizce yanlış tutar üretti; değer nesnesi modelinde yanlış tutar 0, çıplak sayı geçirildiğinde hata 24/24.
- Eşitliğin hoşgörü payı çıplak modelde 3 satırda, değer nesnesi modelinde 1 satırda yazılı; alan kararı ikinci durumda tek noktada durur.
- Beş gönderi aynı ölçüyü paylaşırken bir düzeltme değiştirilebilir kayıtta 4 gönderiyi bozdu, değer nesnesinde 0; dondurulmuş nesnede yerinde değiştirme hata verdi.
- Sekiz ölçüm iki modelde de aynı gruplara dağıldı: kazanç doğrulukta değil, yanlışın yazılamaz hâle gelmesindedir.
- Bedel her kavram için bir tip ve her erişim için bir çağrıdır; tek yerde okunup bırakılan ham değerlerde bu bedel karşılıksızdır.
Sonraki Adım
Buraya kadar gönderi tek başına ele alındı. Alanda tek başına değildir: indirimleri, sözleşmesi ve beyan kalemleri vardır ve aralarında bir kural durur — indirim oranlarının toplamı tavanı aşamaz. Bu kural tek bir nesneye ait değildir, gönderi ile indirimlerinin bütününe aittir. Sonraki ders o bütünün sınırını çizer: kuralı bozan bir durumun kodla kaç ayrı çağrı yolundan üretilebildiğini sayar, sınırı dışa açan ve yalnız kökten geçiren iki modeli karşılaştırır ve bir işlemde kaç ayrı bütünün yazıldığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.