Ders 08 / 14
Nesne Sorumluluğu
Anemik veri taşıyıcısı ile davranışı veriyle tutan düzenin karşılaştırılması: aynı iki kuralın kaç dosyada yazıldığının sayılması, kural değiştirildiğinde arama tabanlı bir güncellemenin bir yeri atlaması ve 40 gönderinin 20'sinde ortaya çıkan tutarsızlığın ölçülmesi.
İçindekiler
Önceki ders iki nesnenin birbirine nasıl bağlanacağını ölçtü. Bu dersin sorusu 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.
Alan taşıyan ama hiçbir kural içermeyen nesnelere anemik (anemic) denir. Bu ders iki düzeni aynı kurallar üzerinde karşılaştırır: bir kuralın kaç yerde yazıldığını sayar ve kural değiştiğinde kaç sonucun birbirinden ayrıldığını ölçer.
Anemik Düzen
Gönderi üç alan taşır ve başka hiçbir şey bilmez.
// anemik/gonderi.mjs — yalnizca alan tasiyan veri nesnesi; hicbir kural burada degil export const gonderi = (agirlikGram, hacimCm3, sozlesmeli = false) => ({ agirlikGram, hacimCm3, sozlesmeli });
Kitaplıkta iki kural vardır. Birincisi ücretli ağırlık: ücrete esas alınan değer, gerçek ağırlık ile hacimden türetilen ağırlığın büyüğüdür. İkincisi sözleşmeli indirimi: sözleşmeli müşteride tutar 0,9 katsayısıyla çarpılır. Üç istemci de bu kuralları kendi içinde uygular.
// anemik/ucretlendirme.mjs — ucretli agirlik ve indirim kurallarini kendisi uygular export const ucret = (g) => { const agirlik = Math.max(g.agirlikGram, g.hacimCm3 / 3); const oran = g.sozlesmeli ? 0.9 : 1; return { agirlik, tutar: Math.round(agirlik * 1.2 * oran) }; };
// anemik/teklif.mjs — ayni iki kurali yeniden yazar export const tahmin = (g) => { const agirlik = Math.max(g.agirlikGram, g.hacimCm3 / 3); const oran = g.sozlesmeli ? 0.9 : 1; return { agirlik, tutar: Math.round(agirlik * 1.5 * oran) }; };
Üçüncü istemci aynı kuralı yazar, ama böleni yerel bir sabite almıştır. Bu, tek başına kusur sayılmayan sıradan bir yazım tercihidir; dersin ilerleyen bölümünde belirleyici hâle gelecektir.
// anemik/plan.mjs — ayni kural, boleni yerel bir sabite alinmis bicimde const HACIM_BOLENI = 3; export const yuk = (g) => { const agirlik = Math.max(g.agirlikGram, g.hacimCm3 / HACIM_BOLENI); return { agirlik, dolulukPayi: Math.round((agirlik / 20000) * 100) }; };
Davranışı Veriyle Tutan Düzen
İkinci düzende iki kural gönderinin kendisine taşınır. Alanlar özeldir; dışarıya kural sonuçları açılır.
// sorumlu/gonderi.mjs — veri ile onu yorumlayan kural ayni birimde export class Gonderi { #agirlikGram; #hacimCm3; #sozlesmeli; constructor(agirlikGram, hacimCm3, sozlesmeli = false) { this.#agirlikGram = agirlikGram; this.#hacimCm3 = hacimCm3; this.#sozlesmeli = sozlesmeli; } ucretliAgirlik() { return Math.max(this.#agirlikGram, this.#hacimCm3 / 3); } indirimOrani() { return this.#sozlesmeli ? 0.9 : 1; } }
Aynı üç istemci artık hesaplamaz, sorar.
// sorumlu/ucretlendirme.mjs — kurali sormaz, gonderiye sorar export const ucret = (g) => { const agirlik = g.ucretliAgirlik(); return { agirlik, tutar: Math.round(agirlik * 1.2 * g.indirimOrani()) }; };
// sorumlu/teklif.mjs — ayni iki soruyu ayni yere sorar export const tahmin = (g) => { const agirlik = g.ucretliAgirlik(); return { agirlik, tutar: Math.round(agirlik * 1.5 * g.indirimOrani()) }; };
// sorumlu/plan.mjs — yalnizca agirligi sorar export const yuk = (g) => { const agirlik = g.ucretliAgirlik(); return { agirlik, dolulukPayi: Math.round((agirlik / 20000) * 100) }; };
Kural Kaç Yerde Yazılı
Ölçüm iki kuralı arar ve kaç dosyada göründüklerini sayar; aynı taramada istemcilerin gönderinin alanlarına kaç kez doğrudan eriştiği de sayılır.
// olcum.mjs — iki kuralin kac dosyada tekrarlandigini ve alanlara kac kez dogrudan erisildigini sayar import { readdirSync, readFileSync } from "node:fs"; const KURAL = [ ["ucretli agirlik", /Math\.max\([^)]*hacimCm3/], ["sozlesmeli indirimi", /sozlesmeli \? 0\.9/], ]; const ALAN = /\bg\.(agirlikGram|hacimCm3|sozlesmeli)\b/g; for (const dizin of ["anemik", "sorumlu"]) { const dosyalar = readdirSync(dizin).sort().map((d) => [d, readFileSync(`${dizin}/${d}`, "utf8")]); console.log(dizin); for (const [ad, kalip] of KURAL) { const yerler = dosyalar.filter(([, k]) => kalip.test(k)).map(([d]) => d); console.log(` ${ad.padEnd(20)} yazildigi dosya = ${yerler.length} ${yerler.join(", ")}`); } const erisim = dosyalar.reduce((t, [, k]) => t + (k.match(ALAN) ?? []).length, 0); console.log(` ${"alana dogrudan erisim".padEnd(20)} = ${erisim}`); }
node olcum.mjs
anemik ucretli agirlik yazildigi dosya = 3 plan.mjs, teklif.mjs, ucretlendirme.mjs sozlesmeli indirimi yazildigi dosya = 2 teklif.mjs, ucretlendirme.mjs alana dogrudan erisim = 8 sorumlu ucretli agirlik yazildigi dosya = 1 gonderi.mjs sozlesmeli indirimi yazildigi dosya = 1 gonderi.mjs alana dogrudan erisim = 0
Ücretli ağırlık kuralı anemik düzende üç dosyada, davranışı veriyle tutan düzende bir dosyada yazılı. Sözleşmeli indirimi ikiye karşı bir. Son satır bunun nedenini gösteriyor: anemik düzende istemciler gönderinin alanlarına sekiz kez doğrudan erişiyor, öteki düzende hiç erişmiyor. Alanı okuyan taraf, o alanın nasıl yorumlanacağını da bilmek zorundadır; kuralın çoğalması bu bilme zorunluluğunun sonucudur.
Kural Değiştiğinde
İki düzen de aynı sonucu üretir. Aşağıdaki denetleyici 40 gönderilik bir ızgarada üç istemciyi çalıştırır ve üçünün bulduğu ücretli ağırlığın aynı olup olmadığını sayar.
// tutarlilik.mjs — uc istemcinin ayni gonderi icin ayni ucretli agirligi bulup bulmadigini sayar import { gonderi } from "./anemik/gonderi.mjs"; import { ucret as aUcret } from "./anemik/ucretlendirme.mjs"; import { tahmin as aTahmin } from "./anemik/teklif.mjs"; import { yuk as aYuk } from "./anemik/plan.mjs"; import { Gonderi } from "./sorumlu/gonderi.mjs"; import { ucret as sUcret } from "./sorumlu/ucretlendirme.mjs"; import { tahmin as sTahmin } from "./sorumlu/teklif.mjs"; import { yuk as sYuk } from "./sorumlu/plan.mjs"; const izgara = []; for (const agirlik of [400, 900, 2500, 6000, 11000]) for (const hacim of [600, 4500, 18000, 45000]) for (const sozlesmeli of [false, true]) izgara.push([agirlik, hacim, sozlesmeli]); const ayrilan = (islemler, kur) => izgara.filter((satir) => { const agirliklar = islemler.map((f) => f(kur(satir)).agirlik); return new Set(agirliklar).size > 1; }).length; console.log(`gonderi sayisi = ${izgara.length}`); console.log(`anemik ayrilan = ${ayrilan([aUcret, aTahmin, aYuk], (s) => gonderi(...s))}`); console.log(`sorumlu ayrilan = ${ayrilan([sUcret, sTahmin, sYuk], (s) => new Gonderi(...s))}`);
node tutarlilik.mjs
gonderi sayisi = 40 anemik ayrilan = 0 sorumlu ayrilan = 0
Şimdi kural değişir: hacimden ağırlığa çeviren bölen 3’ten 4’e iner. Değişikliği yapan
taraf kuralı arar ve bulduğu her yeri günceller. Aşağıdaki blok bunu yapar; sed -i
seçeneği GNU ve BSD sürümlerinde farklı davrandığı için geçici dosya üzerinden yazılmıştır.
for f in $(grep -rl 'hacimCm3 / 3' anemik sorumlu | sort); do sed 's|hacimCm3 / 3|hacimCm3 / 4|' "$f" > "$f.yeni" && mv "$f.yeni" "$f" echo "guncellendi: $f" done node tutarlilik.mjs
guncellendi: anemik/teklif.mjs guncellendi: anemik/ucretlendirme.mjs guncellendi: sorumlu/gonderi.mjs gonderi sayisi = 40 anemik ayrilan = 20 sorumlu ayrilan = 0
Arama anemik düzendeki üç yerin ikisini buldu. Üçüncüsü — böleni yerel bir sabite almış
olan plan.mjs — aramaya takılmadı ve eski kuralla kaldı. Sonuç, 40 gönderinin 20’sinde
ücretlendirmenin bir ağırlık, yükleme planının başka bir ağırlık kullanmasıdır. Aynı
gönderi iki ayrı ağırlıkla anılmaya başladı ve hiçbir yerde hata çıkmadı.
Davranışı veriyle tutan düzende aynı arama tek yeri buldu, çünkü kural tek yerdeydi. Ayrılan sonuç sayısı sıfır kaldı.
Buradaki asıl gözlem, aramanın bir yeri kaçırmış olması değildir; her arama er geç bir yeri kaçırır. Asıl gözlem, anemik düzende kaçırmanın mümkün olmasıdır. Kural bir yerdeyse kaçırılacak ikinci bir yer yoktur.
Sorma, Söyle
Ölçümdeki sekiz doğrudan erişim, kuralın çoğalmasının sebebiydi. Bunu tersine çeviren ilkeye
sorma, söyle (tell, don’t ask) denir: bir nesneden verisini alıp üzerinde karar vermek
yerine, kararı nesneye söyletmek. Anemik istemci g.hacimCm3 alanını alıp bölüyordu;
sorumlu istemci g.ucretliAgirlik() diyerek sonucu istiyordu.
İlkenin sınırı da ölçümde görünür. Bir gönderiye sorulabilecek şey, gönderinin kendi verisiyle yanıtlayabildiği şeydir. Ücretli ağırlık ve indirim oranı böyledir. Bir tarifeye göre tutar hesaplamak değildir: bu, gönderinin bilmediği bir ikinci nesneyi gerektirir. Gönderiye tarifeyi de taşıtmak, sorumluluğu toplamak değil şişirmek olur — kitaplığın her kuralı tek bir sınıfa yığılır. Ölçüt sabittir: bir kural, yalnız kendi verisiyle yanıtlanabiliyorsa o verinin yanına konur.
Alan Taşıyıcısının Yerinde Olduğu Durum
Anemik düzen her yerde kusur değildir. Bir sınırı geçen kayıtlar — ağdan gelen bir gövde, bir veritabanı satırından okunan alanlar, dosyaya yazılan bir çıktı — davranış taşımaz ve taşımamalıdır; işleri biçimi korumaktır, kural yorumlamak değil. Kusur, bu kayıtların kuralları olan alan nesnelerinin yerine geçmesidir: sınırdan gelen kayıt olduğu gibi sistemin içine yayılırsa, onu yorumlayan her nokta kuralı yeniden yazmak zorunda kalır ve ölçülen üçe karşı bir tablosu ortaya çıkar.
Ayrım pratikte tek soruyla verilir: bu nesnenin taşıdığı alanlar bir kural tarafından yorumlanıyor mu? Yorumlanıyorsa kural onunla birlikte durmalıdır. Yorumlanmıyorsa alan taşıyıcısı yerindedir.
Özet
- Anemik nesne yalnız alan taşır, kuralları ayrı işlemcilere bırakır; kuralın veriyle birlikte tutulması bunun karşıtıdır.
- Aynı iki kural anemik düzende üç ve iki dosyada yazılıydı; davranışı veriyle tutan düzende ikisi de tek dosyadaydı.
- Kuralın çoğalmasının kaynağı ölçüldü: anemik istemciler gönderinin alanlarına sekiz kez doğrudan erişiyordu, öteki düzende sıfır.
- Bölen 3’ten 4’e indirildiğinde arama anemik düzendeki üç yerin ikisini buldu; farklı yazılmış üçüncüsü eski kuralla kaldı ve 40 gönderinin 20’sinde ücretlendirme ile yükleme planı ayrı ağırlık kullandı, hiçbir hata çıkmadı.
- Aynı değişiklik davranışı veriyle tutan düzende tek dosyaya dokundu ve 0 ayrılma üretti.
- Kural nesneye ancak nesnenin kendi verisiyle yanıtlanabiliyorsa taşınır; sınırı geçen kayıtların alan taşıyıcısı olarak kalması yerindedir.
Sonraki Adım
Bu konu boyunca birim nesneydi: durum ve onu koruyan davranış bir arada tutuldu. Kapsülleme durumu dışarıdan yazılmaya kapattı, soyutlama temsili gizledi, kalıtım ve bileşim davranışın nasıl paylaşılacağını belirledi, bu ders de kuralı verinin yanına yerleştirdi. Hepsinin ortak varsayımı, durumun bir sahibi olduğu ve o sahibin durumu zaman içinde değiştirdiğidir.
Bir başka model durumu birimden tamamen çıkarır. Veri değişmez; davranış, girdiden çıktıya bir eşlemedir; yan etki programın sınırına itilir. Sonraki konu bu modeli kurar ve saf fonksiyonlarla başlar: aynı girdiye her zaman aynı çıktıyı veren, dışarıda hiçbir iz bırakmayan bir fonksiyonun ne kazandırdığı ve neyin bedelini artırdığı ölçülür.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.