Ders 27 / 30
Kimlik Haritası
Aynı kimliğin tek nesneyle temsil edilmesi: haritasız ve haritalı koşumda üretilen ayrı örnek sayısının, iki kod yolunun aynı kaydı değiştirmesiyle kaybolan alan sayısının ve haritanın bellekte tuttuğu nesne sayısının ölçülmesi, haritanın yaşam süresi uzadığında ortaya çıkan bayat okumanın gösterilmesi.
İçindekiler
İş birimi kirli satırları tablo:kimlik anahtarıyla topladı ve aynı satır üç kez kirlense de
bir kez yazıldı. Bu, yazma tarafında çözülmüş bir tekillik sorunudur. Okuma tarafında aynı
sorun açık duruyor: aynı gönderi kimliği iki kez arandığında iki ayrı nesne doğar. Bir kod
yolu birinci nesnenin indirimini değiştirir, ikinci kod yolu ikinci nesnenin rotasına aktarma
ekler; ikisi de kaydedildiğinde son yazan öteki değişikliği siler.
Kimlik haritası (identity map) bu soruna tek cümlelik bir çözüm koyar: bir kimlik için üretilen nesne saklanır, aynı kimlik yeniden arandığında yeni nesne üretilmez, saklanan döndürülür. Kalıp Veri Erişim Katmanı kursunda üretilen sorgu sayısı üzerinden ölçülmüştü. Burada ölçü nesne düzeyindedir: kaç ayrı örnek doğdu, kaç alan kayboldu, harita bellekte kaç nesne tuttu.
Problem: Aynı Kimlik, İki Nesne
Deponun okuma davranışı sorunun kaynağıdır. Bir depo satırın kendisini değil kopyasını verir; aksi hâlde çağıran taraf saklanan satırı doğrudan değiştirebilirdi.
// bellek-depo.mjs — her okumada satirin yeni bir kopyasini veren sade depo const TABLOLAR = new Map(); const tablo = (ad) => TABLOLAR.get(ad) ?? TABLOLAR.set(ad, new Map()).get(ad); export const depo = { oku: (ad, id) => structuredClone(tablo(ad).get(id) ?? null), yaz: (ad, satir) => { tablo(ad).set(satir.id, structuredClone(satir)); }, };
// gonderi.mjs — kimligi olan alan nesnesi export class Gonderi { constructor({ id, agirlik, indirim, aktarma }) { Object.assign(this, { id, agirlik, indirim, aktarma }); } net() { return Math.round(3900 * this.agirlik * (1 - this.indirim)); } }
Kopya vermek doğru karardır ama bir yan sonucu vardır: iki arama iki bağımsız nesne üretir ve ikisi de aynı kaydı temsil ettiğini bilmez.
Çözüm
Harita, üretim işini kendi içine alır. Nesnenin nasıl üretileceğini bilmez; yalnız daha önce üretilip üretilmediğini bilir.
// kimlik-haritasi.mjs — istek boyunca kimlik basina tek nesne export function kimlikHaritasi() { const harita = new Map(); return { al(tur, id, uret) { const anahtar = `${tur}:${id}`; if (harita.has(anahtar)) return harita.get(anahtar); const nesne = uret(); harita.set(anahtar, nesne); return nesne; }, tutulan: () => harita.size, }; }
Anahtarın tür adını da içermesi zorunludur: iki ayrı türün aynı kimliği taşıması sıradan bir durumdur. Erişim noktası tek bir yerde kalır.
// kaynak.mjs — ayni esleyici; harita verilirse ayni kimlik ayni nesneyi dondurur import { depo } from "./bellek-depo.mjs"; import { Gonderi } from "./gonderi.mjs"; export const kaynak = (harita) => ({ bul(id) { const uret = () => { const satir = depo.oku("gonderi", id); return satir === null ? null : new Gonderi(satir); }; return harita === null ? uret() : harita.al("gonderi", id, uret); }, kaydet: (g) => depo.yaz("gonderi", { id: g.id, agirlik: g.agirlik, indirim: g.indirim, aktarma: g.aktarma }), });
Tekilliğin Sayılması
Ölçüm iki koşum yapar. Beş arama üç ayrı kimliğe gider; ardından aynı kimlik iki kez aranır, iki kod yolu ayrı alanları değiştirir ve ikisi de kaydedilir.
// tekillik-say.mjs — haritasiz ve haritali kosumda ayri ornek, kayip alan ve tutulan nesne import { depo } from "./bellek-depo.mjs"; import { kaynak } from "./kaynak.mjs"; import { kimlikHaritasi } from "./kimlik-haritasi.mjs"; const KIMLIKLER = ["G1", "G2", "G3"]; const ARAMALAR = ["G1", "G2", "G3", "G2", "G1"]; function kosu(ad, harita) { for (const id of KIMLIKLER) depo.yaz("gonderi", { id, agirlik: 3, indirim: 0, aktarma: ["34"] }); const k = kaynak(harita); const ornekler = new Set(ARAMALAR.map((id) => k.bul(id))); const ucret = k.bul("G2"), yon = k.bul("G2"); ucret.indirim = 0.15; yon.aktarma.push("06"); k.kaydet(ucret); k.kaydet(yon); const son = depo.oku("gonderi", "G2"); const beklenen = { indirim: 0.15, aktarma: 2 }; const kayip = (son.indirim === beklenen.indirim ? 0 : 1) + (son.aktarma.length === beklenen.aktarma ? 0 : 1); console.log(`${ad}`); console.log(` ${ARAMALAR.length} arama -> ayri ornek = ${ornekler.size} ayni kimlik ayni nesne mi = ${ucret === yon}`); console.log(` yazma sonrasi indirim = ${son.indirim}, aktarma = ${son.aktarma.length}, kayip alan = ${kayip}`); console.log(` haritada tutulan nesne = ${harita === null ? 0 : harita.tutulan()}`); } kosu("haritasiz", null); kosu("kimlik haritasi ile", kimlikHaritasi());
node tekillik-say.mjs
haritasiz 5 arama -> ayri ornek = 5 ayni kimlik ayni nesne mi = false yazma sonrasi indirim = 0, aktarma = 2, kayip alan = 1 haritada tutulan nesne = 0 kimlik haritasi ile 5 arama -> ayri ornek = 3 ayni kimlik ayni nesne mi = true yazma sonrasi indirim = 0.15, aktarma = 2, kayip alan = 0 haritada tutulan nesne = 3
Beş arama haritasız düzende 5 ayrı nesne üretti, haritalı düzende 3 — kimlik sayısı kadar.
Aynı kimliği iki kez arayan iki kod yolu haritasız düzende iki farklı nesne aldı ve ===
karşılaştırması yanlış sonuç verdi; harita ile aynı nesneyi aldılar.
Kayıp alan sayısı farkın en somut biçimidir: haritasız koşumda indirim değişikliği kayboldu, kaydedilen satırda indirim 0 kaldı. Haritalı koşumda iki değişiklik de aynı nesnenin üzerinde birikti ve ikisi birlikte yazıldı.
Karşılıkta bir sayı daha var: harita 3 nesneyi bellekte tutuyor. Haritasız düzende tutulan nesne 0’dır, üretilen nesneler kullanıldıktan sonra bırakılır. Kalıbın bedeli tam olarak budur ve ikinci bir sonuç doğurur.
Haritanın Yaşam Süresi
Harita bir nesneyi tuttuğu sürece o nesne deponun sonraki hâlini görmez.
// bayat.mjs — harita yasarken depo degisirse okunan deger hangisi olur import { depo } from "./bellek-depo.mjs"; import { kaynak } from "./kaynak.mjs"; import { kimlikHaritasi } from "./kimlik-haritasi.mjs"; for (const [ad, harita] of [["haritasiz", null], ["kimlik haritasi ile", kimlikHaritasi()]]) { depo.yaz("gonderi", { id: "G9", agirlik: 3, indirim: 0.1, aktarma: ["34"] }); const k = kaynak(harita); const ilk = k.bul("G9").indirim; depo.yaz("gonderi", { id: "G9", agirlik: 3, indirim: 0.3, aktarma: ["34"] }); const ikinci = k.bul("G9").indirim; console.log(`${ad.padEnd(20)} ilk okuma = ${ilk}, depo 0.3 oldu, ikinci okuma = ${ikinci}, bayat = ${ikinci !== 0.3}`); }
node bayat.mjs
haritasiz ilk okuma = 0.1, depo 0.3 oldu, ikinci okuma = 0.3, bayat = false kimlik haritasi ile ilk okuma = 0.1, depo 0.3 oldu, ikinci okuma = 0.1, bayat = true
İkinci okuma haritasız düzende yeni değeri, haritalı düzende ilk değeri verdi. Bu bir kusur değil, kalıbın tanımının gereğidir: harita bir nesnenin tekilliğini garanti eder, tazeliğini garanti etmez. Buradan kalıbın uygulanma sınırı çıkar. Harita bir işin — tek bir isteğin ya da tek bir iş biriminin — sınırında yaşar ve o sınır bittiğinde bırakılır. Süreç boyunca yaşayan bir harita bir önbelleğe dönüşür ve tazelik sorusu sorulmaya başlar; o soru kimlik haritasının cevapladığı soru değildir.
Ne Zaman Uygulanmaz
Kalıbın kazancı iki koşuldan doğar: nesnenin bir kimliği olması ve değiştirilebilir olması. İkisinden biri yoksa kazanç sıfırlanır.
Değer nesnelerinde kimlik yoktur; iki bölge katsayısı aynı değeri taşıyorsa birbirinin yerine geçer ve eşitlik değere göre kurulur. Değiştirilemez bir değer nesnesinde iki ayrı örneğin birbirini ezmesi de olanaksızdır, dolayısıyla ölçümdeki kayıp alan sayısı harita olmadan da 0’dır. Kazanç 0, bedel ise haritanın tuttuğu nesne sayısı kadar.
Tek okuma yapan senaryolarda da kazanç yoktur: ayrı örnek sayısı zaten arama sayısına eşittir ve arama sayısı 1’dir. Son sınır ölçekle ilgilidir. Harita süreç yereldir; iki ayrı süreç aynı kaydı yüklerse her birinin haritası kendi nesnesini tutar ve tekillik süreçler arasında korunmaz. Süreçler arası aynı anda yazma sorunu kilitleme konusuna aittir ve Veri Erişim Katmanı kursunda ölçülmüştür.
Özet
- Kimlik haritası bir kimlik için üretilen nesneyi saklar ve aynı kimlik yeniden arandığında yeni nesne üretmek yerine saklananı döndürür.
- Beş arama haritasız düzende 5 ayrı örnek, haritalı düzende kimlik sayısı kadar 3 örnek
üretti; aynı kimliği arayan iki kod yolu haritasız düzende
===karşılaştırmasını geçemedi. - İki kod yolu aynı kaydın ayrı alanlarını değiştirdiğinde haritasız koşumda 1 alan kayboldu; haritalı koşumda iki değişiklik aynı nesnede birikti ve kayıp 0 oldu.
- Bedel bellekte tutulan nesne sayısıdır (3’e karşı 0) ve ikinci bedel bayat okumadır: depo değiştiğinde haritalı düzen ilk değeri vermeye devam etti.
- Harita tek bir işin sınırında yaşar; değer nesnelerinde ve tek okumalı senaryolarda kazanç sıfırlanır, süreçler arasında tekillik korunmaz.
Sonraki Adım
Buraya kadarki dört kalıp iş mantığı ile kalıcılık arasındaki sınırı düzenledi. Alan nesnesi artık kuralı taşıyor, kalıcılığı bilmiyor ve bellekte tekil. Ama bu nesne kitaplığın dışına da çıkacak: bir arayüz onu listeleyecek, bir taşıyıcı sistemi ona rota soracak, bir fatura dökümü ondan üretilecek. Alan nesnesinin dışarıya olduğu gibi verilmesi iç yapıyı dış sözleşme hâline getirir ve iç yapı her değiştiğinde dış sözleşme de değişir. Sonraki ders katmanlar arası veri taşımayı veri aktarım nesneleri ve eşleyicilerle kurar, iç modelde bir alan yeniden adlandırıldığında dokunulan dosya sayısını ve eşlemenin satır başına bedelini sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.