Ders 21 / 30
Arabulucu ve Anımsatıcı
Operatör formundaki dört alanın birbirini doğrudan tanıması ile yönlendirmenin tek nesnede toplanmasının karşılaştırılması: alanlar arası bağ sayısı, güncelleme çağrısı sayısı, bağ sayısının alan sayısıyla büyümesi; ayrıca durum saklamanın dışarıdan erişilen iç alan sayısı ve fişin taşıdığı alan sayısıyla ölçülmesi.
İçindekiler
Ziyaretçi bir işlemi ağacın her düğümüne uyguluyordu; düğümler birbirini tanımıyordu. Operatör ekranında ise nesneler birbirini doğrudan tanıyor. Taşıyıcı seçimi, tarife özeti, indirim kutusu ve teslim tarihi alanı birbirini haberdar ediyor: taşıyıcı değişince tarife yenileniyor, tarife değişince indirim yeniden hesaplanıyor, indirim değişince teslim tarihi güncelleniyor. Her alan kendisinden sonra etkilenen alanları tanıyor ve doğrudan çağırıyor.
Bu dersin iki kalıbı iki ayrı sorunu hedefler. Arabulucu (mediator) alanlar arasındaki yönlendirmeyi tek bir nesneye toplar; alanlar birbirini tanımaz. Anımsatıcı (memento) bir nesnenin durumunu, iç alanlarını dışarıya açmadan saklanabilir bir fişe çevirir. Ölçülecek sayılar alanlar arası bağ sayısı, güncelleme çağrısı sayısı, dışarıdan erişilen iç alan sayısı ve fişin taşıdığı alan sayısıdır.
Alanların Birbirini Tanıması
mkdir -p dogrudan arabulucu animsatici
// dogrudan/form.mjs — her alan kendisinden sonra etkilenen alanlari dogrudan tanir const TARIFE = { yurtici: 8400, ekspres: 12600, yurtdisi: 21000 }; export function form() { const d = { tasiyici: "yurtici", tarife: 0, indirim: 0, teslimGun: 0 }; let cagri = 0; const teslimTarihi = { guncelle() { cagri += 1; d.teslimGun = (d.tasiyici === "ekspres" ? 1 : 3) + (d.indirim >= 10 ? 1 : 0); }, }; const indirim = { guncelle() { cagri += 1; d.indirim = d.tarife > 10000 ? 10 : 5; teslimTarihi.guncelle(); }, }; const tarife = { guncelle() { cagri += 1; d.tarife = TARIFE[d.tasiyici]; indirim.guncelle(); teslimTarihi.guncelle(); }, }; const tasiyici = { sec(ad) { cagri += 1; d.tasiyici = ad; tarife.guncelle(); indirim.guncelle(); teslimTarihi.guncelle(); }, }; return { deger: d, sec: (ad) => tasiyici.sec(ad), sayac: () => cagri }; }
Her alan, kendisinden sonra etkilenebilecek her alanı çağırıyor. Bu, gözlemci dersindekinden farklı bir durumdur: orada haber veren dinleyicilerini tanımıyordu, burada her alan hem haber veriyor hem haber alıyor ve bağlar iki yönlü.
Yönlendirmenin Tek Nesnede Toplanması
Arabulucu sürümünde alanlar birbirini hiç görmez; her biri yalnız kendi hesabını yapar.
// arabulucu/alanlar.mjs — hicbir alan digerini tanimaz, yalniz kendi isini yapar const TARIFE = { yurtici: 8400, ekspres: 12600, yurtdisi: 21000 }; export const alanlar = (say) => ({ tarife: { guncelle(d) { say(); d.tarife = TARIFE[d.tasiyici]; }, }, indirim: { guncelle(d) { say(); d.indirim = d.tarife > 10000 ? 10 : 5; }, }, teslimTarihi: { guncelle(d) { say(); d.teslimGun = (d.tasiyici === "ekspres" ? 1 : 3) + (d.indirim >= 10 ? 1 : 0); }, }, });
// arabulucu/form.mjs — alanlar arasindaki yonlendirme yalniz burada import { alanlar } from "./alanlar.mjs"; const SIRA = ["tarife", "indirim", "teslimTarihi"]; export function form() { const d = { tasiyici: "yurtici", tarife: 0, indirim: 0, teslimGun: 0 }; let cagri = 0; const say = () => { cagri += 1; }; const a = alanlar(say); const bildir = (kaynak) => { const bas = kaynak === "tasiyici" ? 0 : SIRA.indexOf(kaynak) + 1; for (const ad of SIRA.slice(bas)) a[ad].guncelle(d); }; return { deger: d, sec(ad) { say(); d.tasiyici = ad; bildir("tasiyici"); }, sayac: () => cagri, }; }
Değerlerin Eşitliği ve Çağrı Sayısı
// kosum.mjs — iki surumu ayni secimlerle kosar, deger ve cagri sayisini karsilastirir import { form as dogrudanForm } from "./dogrudan/form.mjs"; import { form as arabulucuForm } from "./arabulucu/form.mjs"; let sapma = 0; for (const secim of ["ekspres", "yurtdisi", "yurtici"]) { const a = dogrudanForm(); const b = arabulucuForm(); a.sec(secim); b.sec(secim); const ozet = (f) => Object.entries(f.deger).map(([k, v]) => `${k}=${v}`).join(" "); if (ozet(a) !== ozet(b)) sapma += 1; console.log(`${secim.padEnd(9)} ${ozet(a)} | guncelleme cagrisi dogrudan=${a.sayac()} arabulucu=${b.sayac()}`); } console.log(`iki surum arasindaki deger sapmasi = ${sapma}`);
ekspres tasiyici=ekspres tarife=12600 indirim=10 teslimGun=2 | guncelleme cagrisi dogrudan=8 arabulucu=4 yurtdisi tasiyici=yurtdisi tarife=21000 indirim=10 teslimGun=4 | guncelleme cagrisi dogrudan=8 arabulucu=4 yurtici tasiyici=yurtici tarife=8400 indirim=5 teslimGun=3 | guncelleme cagrisi dogrudan=8 arabulucu=4 iki surum arasindaki deger sapmasi = 0
Değerler birebir aynı, güncelleme çağrısı sayısı sekize karşı dört. Fazla dört çağrı yinelenen tetiklemedir: teslim tarihi bir seçim başına üç kez hesaplanıyor, çünkü onu üç ayrı alan çağırıyor. İlk iki hesabın sonucu üçüncüsü tarafından ezildiği için değer doğru çıkıyor — bu, sırf şansa bağlı bir doğruluktur. Alan sayısı arttıkça yinelenen tetikleme sayısı da artar ve sıra garantisi kaybolur.
Bağ Sayısının Büyümesi
// bag.mjs — alanlar arasindaki bag sayisi ve alan sayisiyla buyumesi import { readFileSync } from "node:fs"; const dogrudanMetin = readFileSync("dogrudan/form.mjs", "utf8"); const arabulucuMetin = readFileSync("arabulucu/form.mjs", "utf8"); const kardesBag = (dogrudanMetin.match(/\b(tarife|indirim|teslimTarihi)\.guncelle\(\)/g) ?? []).length; const arabulucuBag = (arabulucuMetin.match(/^const SIRA = \[(.*)\];$/m)[1].split(",")).length; console.log(`dogrudan alanlar arasi bag=${kardesBag} arabulucu nesnesi=0`); console.log(`arabulucu alanlar arasi bag=0 arabulucunun tanidigi alan=${arabulucuBag}`); console.log(`N=4 icin formul denetimi: N(N-1)/2=${(4 * 3) / 2} N-1=${4 - 1}`); for (const n of [4, 6, 8]) { console.log(`N=${n} dogrudan bag=${(n * (n - 1)) / 2} arabulucu bag=${n - 1}`); }
dogrudan alanlar arasi bag=6 arabulucu nesnesi=0 arabulucu alanlar arasi bag=0 arabulucunun tanidigi alan=3 N=4 icin formul denetimi: N(N-1)/2=6 N-1=3 N=4 dogrudan bag=6 arabulucu bag=3 N=6 dogrudan bag=15 arabulucu bag=5 N=8 dogrudan bag=28 arabulucu bag=7
Ölçülen altı bağ, dört alanlı tam zincir için formülünün verdiği sayıya eşit;
arabulucu sürümünde ölçülen üç bağ formülünü doğruluyor. İki formülün büyüme hızı farklı:
sekiz alanda yirmi sekize karşı yedi. Bağ sayısındaki bu düşüş bedelsiz değildir. Arabulucu bir
nesne ve bir dosya ekler; daha önemlisi, bilgi alanlardan çıkıp arabulucuda birikir. SIRA
dizisi artık formun tüm bağımlılık sırasını taşıyan tek yerdir ve alan sayısı arttıkça bu tek
dosyanın değişim nedeni sayısı da artar. Arabulucunun büyümesi, kalıbın bilinen sonucudur:
bağlar azalır, merkezdeki nesne şişer.
Durumun Saklanması
Operatör bir düzenleme oturumunda birkaç deneme yapıp beğenmezse başlangıca dönmek istiyor. Birinci yol, çağıranın alanları kendisi okuyup geri yazmasıdır.
// dogrudan/oturum.mjs — cagiran ic alanlari kendisi okuyup geri yaziyor export const kopyaAl = (f) => ({ tasiyici: f.deger.tasiyici, tarife: f.deger.tarife, indirim: f.deger.indirim, teslimGun: f.deger.teslimGun, }); export const kopyaYaz = (f, k) => { f.deger.tasiyici = k.tasiyici; f.deger.tarife = k.tarife; f.deger.indirim = k.indirim; f.deger.teslimGun = k.teslimGun; };
Anımsatıcı sürümünde durum hiç dışarı çıkmaz. Nesne bir fiş verir; fiş yalnızca bir sıra numarası taşır ve içeriği dışarıdan okunamaz.
// animsatici/oturum.mjs — durum disariya acilmadan saklanir, fis yalniz bir sira numarasi tasir export function oturum(baslangic) { let durum = { ...baslangic }; const kayitlar = []; return { yaz(ad, deger) { durum[ad] = deger; }, animsat() { kayitlar.push({ ...durum }); return { no: kayitlar.length - 1 }; }, geriYukle(fis) { if (fis.no in kayitlar === false) return false; durum = { ...kayitlar[fis.no] }; return true; }, ozet: () => Object.entries(durum).map(([k, v]) => `${k}=${v}`).join(" "), }; }
// saklama.mjs — iki saklama yontemini karsilastirir import { readFileSync } from "node:fs"; import { form } from "./arabulucu/form.mjs"; import { kopyaAl, kopyaYaz } from "./dogrudan/oturum.mjs"; import { oturum } from "./animsatici/oturum.mjs"; const ozet = (d) => Object.entries(d).map(([k, v]) => `${k}=${v}`).join(" "); const f = form(); f.sec("yurtici"); const fis1 = kopyaAl(f); f.sec("yurtdisi"); const bozuk = ozet(f.deger); kopyaYaz(f, fis1); console.log(`dogrudan fis alan sayisi=${Object.keys(fis1).length} geri yukleme sonrasi: ${ozet(f.deger)}`); const o = oturum({ tasiyici: "yurtici", tarife: 8400, indirim: 5, teslimGun: 3 }); const fis2 = o.animsat(); o.yaz("tasiyici", "yurtdisi"); o.yaz("tarife", 21000); o.yaz("indirim", 10); o.yaz("teslimGun", 4); const yuklendi = o.geriYukle(fis2); console.log(`animsatici fis alan sayisi=${Object.keys(fis2).length} geri yukleme=${yuklendi ? 1 : 0} sonrasi: ${o.ozet()}`); console.log(`degistirilmis ara durum: ${bozuk}`); const disErisim = (yol) => (readFileSync(yol, "utf8").match(/f\.deger\.\w+/g) ?? []).length; console.log(`disaridan erisilen ic alan: dogrudan=${disErisim("dogrudan/oturum.mjs")} animsatici=${disErisim("animsatici/oturum.mjs")}`); console.log(`gecersiz fis denemesi: animsatici geriYukle({ no: 9 })=${o.geriYukle({ no: 9 }) ? 1 : 0}`);
dogrudan fis alan sayisi=4 geri yukleme sonrasi: tasiyici=yurtici tarife=8400 indirim=5 teslimGun=3
animsatici fis alan sayisi=1 geri yukleme=1 sonrasi: tasiyici=yurtici tarife=8400 indirim=5 teslimGun=3
degistirilmis ara durum: tasiyici=yurtdisi tarife=21000 indirim=10 teslimGun=4
disaridan erisilen ic alan: dogrudan=8 animsatici=0
gecersiz fis denemesi: animsatici geriYukle({ no: 9 })=0
İki yöntem de aynı başlangıç durumunu geri getirdi; ara durumun gerçekten değiştiği üçüncü
satırda görülüyor. Fark ölçüde: dışarıdan erişilen iç alan sayısı sekize karşı sıfır, fişin
taşıdığı alan sayısı dörde karşı bir. Birinci yöntem, alan başına bir okuma ve bir yazma
gerektirdiği için beşinci bir alan eklendiğinde iki satır daha büyür; anımsatıcı sürümünde
{ ...durum } alan sayısından bağımsızdır ve düzenlenmez.
Bedel iki yerde. Birincisi bellektir: her fiş durumun tam bir kopyasını saklar, kayıt sayısı kadar kopya birikir ve bunları temizleyecek bir kural yoktur. İkincisi fişin geçerliliğidir: son satır, var olmayan bir numarayla yapılan geri yükleme denemesinin sessizce başarısız olduğunu gösteriyor. Fiş içeriğini gizlediği için çağıran fişin hâlâ anlamlı olup olmadığını sınayamaz; kalıp bu sınamayı nesnenin kendisine bırakır.
Özet
- Arabulucu alanlar arasındaki yönlendirmeyi tek nesneye toplar; alanlar birbirini tanımaz, yalnız kendi hesabını yapar.
- Değerler iki sürümde birebir aynı çıktı; güncelleme çağrısı sayısı 8’den 4’e indi, fazladan dört çağrı yinelenen tetiklemeydi.
- Alanlar arası bağ sayısı 6’dan 3’e indi; ölçüm ve formüllerini doğruladı, sekiz alanda fark 28’e karşı 7 oluyor. Bedel, bağımlılık sırasını tek başına taşıyan bir arabulucu nesnesidir.
- Anımsatıcı durumu dışarı açmadan saklar: dışarıdan erişilen iç alan sayısı 8’den 0’a, fişin taşıdığı alan sayısı 4’ten 1’e indi ve iki yöntem aynı başlangıç durumunu geri getirdi.
- Anımsatıcının bedeli her fişin tam bir durum kopyası tutması ve geçersiz bir fişin sessizce başarısız olmasıdır.
Sonraki Adım
Arabulucu bir sırayı, anımsatıcı bir kopyayı saklıyor; ikisi de veri tutuyor ama veri hâlâ
kodun içinde yazılı. Tarife kuralları tarafında bunun sınırına gelindi: her yeni koşullu kural
bir dosya değişikliği demek ve kuralları yazan kişi geliştirici değil, tarife birimi. Kuralın
metin olarak yazılıp çalışma zamanında yorumlanması isteniyor. Aynı bölümde ikinci bir soru var:
bir gönderiye hiçbir indirim uygulanmadığında sonucun null olmasıyla, hiçbir şey yapmayan bir
indirim nesnesi olması arasındaki fark nedir. Sonraki ders küçük bir kural dilini yorumlayan
nesne ağacını kurar, yeni kural eklendiğinde düzenlenen dosya sayısını ölçer ve boş değer
denetimi sayısını boş nesneyle karşılaştırır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.