İçeriğe geç
academia.sh

Ders 13 / 30

Vekil

Erişimin araya girerek denetlenmesi: tarife kaynağına giden çağrı sayısının vekilsiz ve önbellekleyen vekilli bağlamada sayılması, sanal vekille kaynağın ilk isteğe kadar kurulmaması, koruyan vekille nesne düzeyinde araya girme ve bedelin dolaylılık basamağı ile bayat okuma sayısıyla ölçülmesi.

İçindekiler

Sinek siklette bir nesne çok sayıda kayıt tarafından paylaşıldı, ama erişim serbestti: kayıt tarife alanını okuyup doğrudan kullanıyordu. Bu dersin problemi erişimin kendisidir.

Tarife tabloları artık kitaplığın içinde değil, ayrı bir kaynakta duruyor ve o kaynağa gitmek pahalıdır. Üç istek birden çıkıyor: kaynak hiç kullanılmayacaksa hiç kurulmasın, aynı tablo ikinci kez istendiğinde tekrar getirilmesin, belli tarifelere erişim kapatılabilsin. Üçünün de ortak yanı, asıl nesnenin gövdesine dokunmamaktır. Vekil (proxy) asıl nesneyle aynı arayüzü sunan ve çağrıyı ona geçirirken araya giren bir nesnedir. Ölçü kaynağa giden çağrı sayısıdır; bedel dolaylılık basamağı ve bayat okuma sayısıyla ölçülür.

Problem: Her Kayıt Kaynağa Gidiyor

Kaynak, kuruluşunu ve her çağrısını sayıyor. Sayaçlar ölçümün aracıdır; gerçek bir kaynakta bu sayılar ağ isteği ya da dosya okuması demektir.

// kaynak/tarife-kaynagi.mjs — gercek kaynak: kurulusu ve her cagrisi sayilir
const TABLOLAR = {
  standart: { kademeler: [[1, 4990], [5, 8490], [15, 14990], [30, 24990]], katsayi: { 34: 100, "06": 118, 35: 120, 65: 145 } },
  ekonomik: { kademeler: [[1, 3990], [5, 6990], [15, 11990], [30, 19990]], katsayi: { 34: 100, "06": 118, 35: 120, 65: 145 } },
  hizli: { kademeler: [[1, 6490], [5, 10990], [15, 18990], [30, 31990]], katsayi: { 34: 100, "06": 118, 35: 120, 65: 145 } },
};

let kurulan = 0, cagri = 0;

export const sayaclar = () => ({ kurulan, cagri });
export const sayaclariSifirla = () => { kurulan = 0; cagri = 0; };

export function kaynakKur() {
  kurulan += 1;
  return {
    getir(kod) {
      cagri += 1;
      const t = TABLOLAR[kod];
      if (t === undefined) throw new RangeError(`bilinmeyen tarife: ${kod}`);
      return t;
    },
  };
}

export function kaynaktaGuncelle(kod, zamKatsayisi) {
  TABLOLAR[kod] = {
    kademeler: TABLOLAR[kod].kademeler.map(([u, f]) => [u, Math.round(f * zamKatsayisi)]),
    katsayi: TABLOLAR[kod].katsayi,
  };
}
// veri.mjs — ham gonderi satirlari; degerler dizinden turetilir, rastgelelik yok
const POSTA = ["34100", "06500", "35400", "65100"];
const TARIFE = ["standart", "ekonomik", "hizli"];

export function* hamSatirlar(n) {
  for (let i = 0; i < n; i += 1) {
    yield {
      kod: `G-${i}`,
      agirlik: 0.5 + (i % 59) / 2,
      postaKodu: POSTA[i % POSTA.length],
      tarifeKodu: TARIFE[i % TARIFE.length],
    };
  }
}

İstemci kaynağı dışarıdan alır ve türünü bilmez. Bu dersin bütün ölçümlerinde aynı dosya kullanılır; değişen tek şey ona hangi nesnenin geçirildiğidir.

// istemci.mjs — iki baglamada da ayni dosya: kaynagi disaridan alir, turunu bilmez
export function ucretlendir(kaynak, satirlar) {
  let toplam = 0;
  for (const s of satirlar) {
    const t = kaynak.getir(s.tarifeKodu);
    const k = t.kademeler.find(([ust]) => s.agirlik <= ust) ?? [0, 24990];
    toplam += Math.round((k[1] * (t.katsayi[s.postaKodu.slice(0, 2)] ?? 165)) / 100);
  }
  return toplam;
}

Çözüm: Aynı Arayüzü Sunan Üç Vekil

Önbellekleyen vekil, aynı kod için kaynağa bir kez gider. Arayüzü kaynağınkiyle aynıdır: getir(kod).

// vekil/onbellek.mjs — onbellekleyen vekil: ayni arayuz, ayni kod ikinci kez kaynaga gitmez
export const onbellekVekil = (ic) => {
  const bellek = new Map();
  return {
    ic,
    getir(kod) {
      if (!bellek.has(kod)) bellek.set(kod, ic.getir(kod));
      return bellek.get(kod);
    },
    bellekBoyu: () => bellek.size,
  };
};
// vekil/tembel.mjs — sanal vekil: kaynak ilk istege kadar kurulmaz
export const tembelVekil = (kaynakKurucu) => {
  let ic = null;
  return {
    get ic() { return ic ?? undefined; },
    getir(kod) {
      ic ??= kaynakKurucu();
      return ic.getir(kod);
    },
  };
};
// vekil/koruma.mjs — koruyan vekil: nesne duzeyinde araya girer, izinli kod listesine bakar
export const korumaVekili = (ic, izinli) => ({
  ic,
  getir(kod) {
    if (!izinli.includes(kod)) throw new RangeError(`erisim kapali: ${kod}`);
    return ic.getir(kod);
  },
});

Koruyan vekilin yaptığı iş nesne düzeyinde araya girmedir: hangi nesnenin hangi çağrıyı geçireceğine karar verir. Kimin hangi tarifeye erişebileceğini belirleyen kurallar — kimlik, rol, izin modeli — Kimlik Doğrulama ve Yetkilendirme kursunda ele alınmıştır; buradaki liste o kararın sonucunu taşıyan bir veridir, kuralın kendisi değil.

Çağrı Sayısının Ölçülmesi

// kosum.mjs — vekilsiz ve onbellekli baglama: kaynaga giden cagri sayisi ve toplam ucret
import { hamSatirlar } from "./veri.mjs";
import { kaynakKur, sayaclar, sayaclariSifirla } from "./kaynak/tarife-kaynagi.mjs";
import { onbellekVekil } from "./vekil/onbellek.mjs";
import { ucretlendir } from "./istemci.mjs";

const N = 20000;
const derinlik = (n) => (n?.ic === undefined ? 1 : 1 + derinlik(n.ic));

sayaclariSifirla();
const dogrudanKaynak = kaynakKur();
const a = ucretlendir(dogrudanKaynak, hamSatirlar(N));
const s1 = sayaclar();

sayaclariSifirla();
const vekilliKaynak = onbellekVekil(kaynakKur());
const b = ucretlendir(vekilliKaynak, hamSatirlar(N));
const s2 = sayaclar();

console.log(`vekilsiz: toplam=${a}  kaynak cagrisi=${s1.cagri}  derinlik=${derinlik(dogrudanKaynak)}`);
console.log(`vekilli : toplam=${b}  kaynak cagrisi=${s2.cagri}  derinlik=${derinlik(vekilliKaynak)}  ` +
  `onbellek=${vekilliKaynak.bellekBoyu()}`);
console.log(`toplam farki = ${a - b}  gonderi = ${N}`);
vekilsiz: toplam=463093345  kaynak cagrisi=20000  derinlik=1
vekilli : toplam=463093345  kaynak cagrisi=3  derinlik=2  onbellek=3
toplam farki = 0  gonderi = 20000

Kaynağa giden çağrı 20.000’den 3’e indi; toplam ücret kuruşu kuruşuna aynı. Üç sayı, üç farklı tarife koduna karşılık gelir: önbellek boyu iç durum çeşidi kadardır. Kazancın tamamı istemci dosyasında tek satır değişmeden elde edildi; iki koşumda istemci.mjs aynı dosyadır ve getir çağrısı da aynıdır. Değişen şey yalnız bileşim kökünde hangi nesnenin geçirildiğidir. Vekilin arayüzü kaynağınkiyle aynı olmasaydı bu mümkün olmazdı — kalıbın tek zorunlu kuralı budur.

Üç Vekilin Üst Üste Konması

Vekiller de aynı arayüzü döndürdüğü için birbirini sarabilir. Aşağıdaki zincir dıştan içe koruma, önbellek ve sanal vekildir; en içte kaynağın kendisi vardır ve daha kurulmamıştır.

// zincir.mjs — uc vekil ust uste: koruma -> onbellek -> tembel kaynak
import { hamSatirlar } from "./veri.mjs";
import { kaynakKur, sayaclar, sayaclariSifirla } from "./kaynak/tarife-kaynagi.mjs";
import { onbellekVekil } from "./vekil/onbellek.mjs";
import { tembelVekil } from "./vekil/tembel.mjs";
import { korumaVekili } from "./vekil/koruma.mjs";
import { ucretlendir } from "./istemci.mjs";

const derinlik = (n) => (n?.ic === undefined ? 1 : 1 + derinlik(n.ic));

sayaclariSifirla();
const zincir = korumaVekili(onbellekVekil(tembelVekil(kaynakKur)), ["standart", "ekonomik", "hizli"]);
console.log(`ilk cagri oncesi: kaynak kurulan=${sayaclar().kurulan}  derinlik=${derinlik(zincir)}`);

const toplam = ucretlendir(zincir, hamSatirlar(20000));
console.log(`ilk cagri sonrasi: kaynak kurulan=${sayaclar().kurulan}  cagri=${sayaclar().cagri}  ` +
  `derinlik=${derinlik(zincir)}  toplam=${toplam}`);

const kapali = korumaVekili(onbellekVekil(tembelVekil(kaynakKur)), ["standart"]);
try {
  ucretlendir(kapali, hamSatirlar(3));
  console.log("kapali zincir: butun kodlar gecti");
} catch (e) {
  console.log(`kapali zincir: ${e.message}`);
}
ilk cagri oncesi: kaynak kurulan=0  derinlik=3
ilk cagri sonrasi: kaynak kurulan=1  cagri=3  derinlik=4  toplam=463093345
kapali zincir: erisim kapali: ekonomik

İlk çağrıdan önce kaynak hiç kurulmadı: sayaç 0. Zincir derinliği 3’ken ilk çağrıdan sonra 4 oldu, çünkü kaynak o anda kurulup zincire katıldı. Toplam ücret tek vekilli koşumla aynıdır. İzinli listesi daraltıldığında ikinci tarife kodunda erişim kapandı ve hata istemciye kadar çıktı; koruyan vekil kararı kaynağa hiç gitmeden verdi.

Bedel: Dolaylılık ve Bayat Okuma

Zincir derinliği ölçümü bedelin ilk yarısıdır: bir tablo isteği için izlenecek gövde sayısı 1’den 4’e çıktı. İkinci yarısı önbelleğin doğasındadır — kaynak değişirse vekil eski değeri vermeye devam eder.

// bayat.mjs — onbellegin bedeli: kaynak degisince vekil eski tabloyu vermeye devam eder
import { hamSatirlar } from "./veri.mjs";
import { kaynakKur, kaynaktaGuncelle, sayaclariSifirla } from "./kaynak/tarife-kaynagi.mjs";
import { onbellekVekil } from "./vekil/onbellek.mjs";
import { ucretlendir } from "./istemci.mjs";

const N = 20000;
sayaclariSifirla();
const vekilli = onbellekVekil(kaynakKur());
const dogrudan = kaynakKur();

console.log(`zam oncesi : vekilli=${ucretlendir(vekilli, hamSatirlar(N))}  ` +
  `dogrudan=${ucretlendir(dogrudan, hamSatirlar(N))}`);

kaynaktaGuncelle("standart", 1.1);

const sonraVekilli = ucretlendir(vekilli, hamSatirlar(N));
const sonraDogrudan = ucretlendir(dogrudan, hamSatirlar(N));
const etkilenen = [...hamSatirlar(N)].filter((s) => s.tarifeKodu === "standart").length;
console.log(`zam sonrasi: vekilli=${sonraVekilli}  dogrudan=${sonraDogrudan}`);
console.log(`bayat okuma = ${etkilenen} gonderi, eksik hesaplanan = ${sonraDogrudan - sonraVekilli} kurus`);
zam oncesi : vekilli=463093345  dogrudan=463093345
zam sonrasi: vekilli=463093345  dogrudan=478135357
bayat okuma = 6667 gonderi, eksik hesaplanan = 15042012 kurus

Standart tarifeye yüzde on zam geldikten sonra vekilli hesap eski tabloyu kullandı: 6667 gönderi bayat okundu ve toplam 15.042.012 kuruş eksik hesaplandı. Hiçbir hata atılmadı. Kazanç ile bedel aynı mekanizmadan doğuyor: çağrı sayısını 20.000’den 3’e indiren şey, tazeliği 3 okumaya bağlayan şeydir. Önbellekleyen vekil kullanılıyorsa geçerlilik süresi ya da geçersiz kılma yolu tasarımın parçasıdır; yoksa kalıp doğruluğu sessizce bozar.

Vekilin dekoratörden ayrımı da burada nettir. İkisi de aynı arayüzü korur ve araya girer, ama dekoratör sonucun üstüne bir davranış ekler — yakıt farkı, vergi — ve zincirin sırası sonucu değiştirir. Vekil sonucu değiştirmez; erişimi yönetir: ne zaman gidileceğine, kaç kez gidileceğine, gidilip gidilmeyeceğine karar verir. Bu yüzden vekilli ve vekilsiz koşumların toplamı aynı çıkmak zorundadır — çıkmadığı yer, yukarıdaki bayatlık ölçümünde olduğu gibi, bir kusurun kanıtıdır.

İki durumda uygulanmaz. Erişim ucuzsa ve tekrar yoksa — tablo bellekte duruyorsa, her kod bir kez isteniyorsa — önbellekleyen vekil yalnız bir eşleme ve bir dolaylılık basamağı ekler. Kaynağın taze olması zorunluysa ve geçersiz kılma yolu yoksa önbellek yasaktır; o durumda vekilden beklenen kazanç, ölçülemeyen bir doğruluk riskiyle ödenir.

Özet

  • Vekilin çözdüğü problem, asıl nesnenin gövdesine dokunmadan erişimi denetlemektir; zorunlu kural vekilin asıl nesneyle aynı arayüzü sunmasıdır.
  • Önbellekleyen vekil kaynağa giden çağrıyı 20.000’den 3’e indirdi; toplam ücret aynı kaldı ve istemci dosyasında tek satır değişmedi — değişen yer bileşim köküdür.
  • Sanal vekil kaynağı ilk isteğe kadar hiç kurmadı (kuruluş sayacı 0), koruyan vekil kapalı bir tarife kodunda kararı kaynağa gitmeden verdi.
  • Bedel iki sayıyla ölçüldü: bir istek için izlenecek gövde sayısı 1’den 4’e çıktı ve kaynak değiştiğinde 6667 gönderi bayat okuyup 15.042.012 kuruş eksik hesaplandı.
  • Vekil erişimi yönetir, dekoratör davranış ekler: vekilli ve vekilsiz koşumun sonucu aynı olmak zorundadır. Erişim ucuzsa ya da tazelik zorunluysa kalıp karşılığını vermez.

Sonraki Adım

Yapısal kalıplar nesnelerin nasıl birleştirileceğini düzenledi: bağdaştırıcı uyumsuz iki arayüzü aynı sözleşmeye çevirdi, köprü iki bağımsız ekseni ayırdı, bileşik ağacı tek biçim ele aldı, dekoratör davranışı katmanladı, cephe alt sistemin önüne tek kapı koydu, sinek siklet değişmeyeni paylaştı, vekil ise araya girip çağrıyı aynı arayüzle ilerisine geçirdi. Bu son cümlede bir sınır saklı: vekilde araya giren nesnenin işi çağrıyı geçirmekti, hesabın kendisi hiç değişmiyordu — zaten değişmemesi gerekiyordu.

Sıradaki soru birleşme değil, davranışın seçilmesi: aynı arayüzün arkasındaki algoritma çalışma zamanında değiştirilebilir mi? Ücretlendirme kitaplığında bu somut bir istektir — aynı gönderi kümesinin standart tarifeyle, ekspres tarifeyle ve hacimden türetilen ücretli ağırlık tarifesiyle aynı koşumda fiyatlanması. Sonraki ders algoritmayı bir nesneye alıp çağrı yerine parametre olarak geçiren strateji kalıbını yazar ve tek koşumda yan yana durabilen algoritma sayısıyla yeni algoritma eklendiğinde düzenlenen dosya sayısını ölçer.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat