İçeriğe geç
academia.sh

Ders 15 / 30

Gözlemci

Teslimat durumu değiştiğinde çalışan dört işin doğrudan çağrılması ile abonelik üzerinden bildirilmesinin karşılaştırılması: giden bağ sayısı, ithal kapanışı boyutu, beşinci iş eklendiğinde düzenlenen dosyanın hangi katmanda olduğu ve kalıbın bedeli olarak statik çağrı sayısının sıfıra düşmesi.

İçindekiler

Tarife nesnesi bir hesabı taşıyordu ve çağıran onu doğrudan çağırıyordu: bağ tek yönlü ve görünürdü. Kitaplığın başka bir yerinde ilişki ters yönde kurulmak zorunda. Bir teslimatın durumu değiştiğinde dört iş yapılmalı: günlük satırı yazılmalı, müşteriye bildirim düşülmeli, ölçüm sayacı artırılmalı, faturalama kaydı güncellenmeli. Bu dördünü teslimat modülü kendisi çağırırsa dördünü de ithal etmek zorunda kalır.

Gözlemci (observer) kalıbı, durum değişimini haber veren nesnenin haberi kimin aldığını bilmemesini sağlar: dinleyiciler kendilerini kaydeder, haber veren yalnızca kayıtlı olanlara bildirir. Bu dersteki ele alış süreç içi nesne bildirimidir; aynı fikir Backend müfredatında iki kez daha görünmüştü — alan olayları Veri Erişim Katmanı kursunda, yayımla–abone ol düzeni Mesajlaşma konusunda. Kuyruk, teslim garantisi ve dayanıklılık oralara aittir; burada bildirim aynı süreçte, aynı çağrı yığıtında gerçekleşir. Ölçülecek sayılar giden bağ sayısı, ithal kapanışı boyutu ve düzenlenen dosyanın katmanıdır.

Dört İş, İki Düzen

Dinleyici modülleri iki sürümde birebir aynıdır; her biri yaptığı işi tek satırla bildirir.

mkdir -p dogrudan gozlemci
for k in dogrudan gozlemci; do
  cat > "$k/gunluk.mjs" <<'SON'
export const gunlukYaz = (olay) => console.log(`  gunluk    ${olay.kod} ${olay.durum}`);
SON
  cat > "$k/bildirim.mjs" <<'SON'
export const bildirimGonder = (olay) => console.log(`  bildirim  ${olay.kod} ${olay.durum}`);
SON
  cat > "$k/olcum.mjs" <<'SON'
export const olcumArtir = (olay) => console.log(`  olcum     ${olay.kod} ${olay.durum}`);
SON
  cat > "$k/faturalama.mjs" <<'SON'
export const faturayaIsle = (olay) => console.log(`  fatura    ${olay.kod} ${olay.durum}`);
SON
done

Birinci düzende teslimat dördünü ithal eder ve sırayla çağırır.

// dogrudan/teslimat.mjs — dort isi kendisi ithal edip cagirir
import { gunlukYaz } from "./gunluk.mjs";
import { bildirimGonder } from "./bildirim.mjs";
import { olcumArtir } from "./olcum.mjs";
import { faturayaIsle } from "./faturalama.mjs";

export const teslimat = (kod) => {
  let durum = "olusturuldu";
  return {
    kod,
    get durum() {
      return durum;
    },
    durumDegistir(yeni) {
      durum = yeni;
      const olay = { kod, durum: yeni };
      gunlukYaz(olay);
      bildirimGonder(olay);
      olcumArtir(olay);
      faturayaIsle(olay);
    },
  };
};

İkinci düzende teslimat hiçbir işi ithal etmez. abone yöntemi bir dinleyici alır ve aboneliği sonlandıran bir işlev döndürür; durumDegistir yalnızca kayıtlı dinleyicileri gezer.

// gozlemci/teslimat.mjs — hicbir isi ithal etmez, yalnizca bildirir
export const teslimat = (kod) => {
  let durum = "olusturuldu";
  const aboneler = [];
  return {
    kod,
    get durum() {
      return durum;
    },
    abone(dinleyici) {
      aboneler.push(dinleyici);
      return () => aboneler.splice(aboneler.indexOf(dinleyici), 1);
    },
    durumDegistir(yeni) {
      durum = yeni;
      const olay = { kod, durum: yeni };
      for (const dinleyici of aboneler) dinleyici(olay);
    },
  };
};

İthaller kaybolmadı; yer değiştirdi. Dördünü tanıyan tek dosya artık aboneliği kuran dosyadır.

// gozlemci/kur.mjs — abonelikler yalnizca burada kurulur
import { teslimat } from "./teslimat.mjs";
import { gunlukYaz } from "./gunluk.mjs";
import { bildirimGonder } from "./bildirim.mjs";
import { olcumArtir } from "./olcum.mjs";
import { faturayaIsle } from "./faturalama.mjs";

export function kurulmusTeslimat(kod) {
  const t = teslimat(kod);
  for (const dinleyici of [gunlukYaz, bildirimGonder, olcumArtir, faturayaIsle]) t.abone(dinleyici);
  return t;
}

Davranışın Eşitliği ve Bir Kayıp Yolu

Ölçüm ancak iki düzen aynı işi yapıyorsa anlamlıdır. Sürücü betik durum değişiminde tetiklenen iş sayısını, dinleyicilerin yazdığı satırları sayarak bulur. Üçüncü koşum aboneliği kurmadan çıplak teslimatı çağırır.

// kosum.mjs — durum degisiminde tetiklenen is sayisini sayar
import { teslimat as dogrudanTeslimat } from "./dogrudan/teslimat.mjs";
import { teslimat as ciplakTeslimat } from "./gozlemci/teslimat.mjs";
import { kurulmusTeslimat } from "./gozlemci/kur.mjs";

const asil = console.log;
let sayac = 0;
console.log = (...a) => {
  sayac += 1;
  asil(...a);
};

const say = (etiket, uret) => {
  sayac = 0;
  asil(`${etiket}:`);
  uret().durumDegistir("yolda");
  asil(`  ${etiket.padEnd(26)} tetiklenen is = ${sayac}`);
};

say("dogrudan", () => dogrudanTeslimat("TS-1"));
say("gozlemci (kayitli)", () => kurulmusTeslimat("TS-2"));
say("gozlemci (kayit yapilmamis)", () => ciplakTeslimat("TS-3"));
dogrudan:
  gunluk    TS-1 yolda
  bildirim  TS-1 yolda
  olcum     TS-1 yolda
  fatura    TS-1 yolda
  dogrudan                   tetiklenen is = 4
gozlemci (kayitli):
  gunluk    TS-2 yolda
  bildirim  TS-2 yolda
  olcum     TS-2 yolda
  fatura    TS-2 yolda
  gozlemci (kayitli)         tetiklenen is = 4
gozlemci (kayit yapilmamis):
  gozlemci (kayit yapilmamis) tetiklenen is = 0

İlk iki koşum dörde dört eşit: düzenler aynı davranışı taşıyor. Üçüncü koşum kalıbın bedelinin en sert kalemini veriyor. Abonelik kurulmadığında durum değişimi sessizce tamamlanır, hiçbir hata çıkmaz ve dört iş de yapılmamış olur. Doğrudan çağıran düzende böyle bir yol yoktur; ithal satırı varsa çağrı da vardır.

Bağ Sayısı ve İthal Kapanışı

İkinci ölçü statiktir. Betik her kökün doğrudan ithal ettiği dosya sayısını, ithal kapanışının boyutunu ve gövdede görünen olay çağrısı sayısını sayar.

// grafik.mjs — giden bag sayisi, ithal kapanisi ve statik cagri sayisi
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";

const ithaller = (yol) =>
  [...readFileSync(yol, "utf8").matchAll(/from "(\.[^"]+)"/g)]
    .map((m) => normalize(join(dirname(yol), m[1])));

function kapanis(kok) {
  const gorulen = new Set([kok]);
  const yigit = [kok];
  while (yigit.length > 0) {
    for (const k of ithaller(yigit.pop())) {
      if (gorulen.has(k) === false) {
        gorulen.add(k);
        yigit.push(k);
      }
    }
  }
  return gorulen;
}

const cagri = (yol) => (readFileSync(yol, "utf8").match(/^ {6}\w+\(olay\);$/gm) ?? []).length;

for (const kok of ["dogrudan/teslimat.mjs", "gozlemci/teslimat.mjs", "gozlemci/kur.mjs"]) {
  console.log(
    `${kok.padEnd(22)} giden bag=${ithaller(kok).length}  ithal kapanisi=${kapanis(kok).size}  statik cagri=${cagri(kok)}`,
  );
}
dogrudan/teslimat.mjs  giden bag=4  ithal kapanisi=5  statik cagri=4
gozlemci/teslimat.mjs  giden bag=0  ithal kapanisi=1  statik cagri=0
gozlemci/kur.mjs       giden bag=5  ithal kapanisi=6  statik cagri=0

Teslimat modülünün giden bağ sayısı 4’ten 0’a, ithal kapanışı 5’ten 1’e indi. Bu, tasarım ilkeleri kursunda ölçülen bağımlılık yönü kazancının kalıp düzeyindeki karşılığıdır: alan nesnesi artık günlük, bildirim, ölçüm ve faturalama modüllerini tanımıyor, bu yüzden onlar değiştiğinde yeniden derlenmesi ya da testinin sahte bağımlılık kurması gerekmiyor. Bedel kapanış toplamında görünür: dört bağ yok olmadı, bileşim köküne taşındı ve orada beş oldu.

Üçüncü sütun bedelin okuma tarafını veriyor. Doğrudan düzende durumDegistir gövdesine bakan biri dört çağrıyı sayabilir; gözlemci düzeninde aynı gövdede sayılabilecek çağrı sayısı sıfırdır. Durum değişiminde ne olduğunu öğrenmek için gövdeyi okumak yetmez, abonelik kuran dosyayı bulmak gerekir. Bu, strateji dersinde ölçülen dolaylılık basamağının aynısıdır; oradaki olası gövde sayısı burada kayıtlı dinleyici sayısına karşılık gelir ve çalışma zamanına kadar bilinmez.

Beşinci İş Eklendiğinde

Sözleşmeli müşteri indirimlerinin denetlenmesi beşinci iş olarak isteniyor. Betik iki ağacı kopyalar, eklemeyi yapar ve iki sürümü de koşar.

// ekle.mjs — besinci dinleyiciyi iki surume de ekler ve ikisini de kosar
import { cpSync, readFileSync, writeFileSync } from "node:fs";

cpSync("dogrudan", "dogrudan-yeni", { recursive: true });
cpSync("gozlemci", "gozlemci-yeni", { recursive: true });

const YENI = "export const indirimDenetle = (olay) => console.log(`  indirim   ${olay.kod} ${olay.durum}`);\n";
writeFileSync("dogrudan-yeni/indirim.mjs", YENI);
writeFileSync("gozlemci-yeni/indirim.mjs", YENI);

const ITHAL = 'import { faturayaIsle } from "./faturalama.mjs";';
const degistir = (yol, arayan, yeni) =>
  writeFileSync(yol, readFileSync(yol, "utf8").replace(arayan, yeni));

degistir("dogrudan-yeni/teslimat.mjs", ITHAL, `${ITHAL}\nimport { indirimDenetle } from "./indirim.mjs";`);
degistir("dogrudan-yeni/teslimat.mjs", "      faturayaIsle(olay);", "      faturayaIsle(olay);\n      indirimDenetle(olay);");
degistir("gozlemci-yeni/kur.mjs", ITHAL, `${ITHAL}\nimport { indirimDenetle } from "./indirim.mjs";`);
degistir("gozlemci-yeni/kur.mjs", "faturayaIsle]", "faturayaIsle, indirimDenetle]");

const { teslimat } = await import("./dogrudan-yeni/teslimat.mjs");
const { kurulmusTeslimat } = await import("./gozlemci-yeni/kur.mjs");
const asil = console.log;
let sayac = 0;
console.log = () => {
  sayac += 1;
};
teslimat("TS-4").durumDegistir("dagitimda");
const a = sayac;
sayac = 0;
kurulmusTeslimat("TS-5").durumDegistir("dagitimda");
const b = sayac;
console.log = asil;
console.log(`dogrudan-yeni tetiklenen is = ${a}`);
console.log(`gozlemci-yeni tetiklenen is = ${b}`);
dogrudan-yeni tetiklenen is = 5
gozlemci-yeni tetiklenen is = 5
for k in dogrudan gozlemci; do
  duzenlenen=$(diff -rq "$k" "$k-yeni" | grep -c '^Files')
  eklenen=$(diff -rq "$k" "$k-yeni" | grep -c '^Only in')
  echo "$k: duzenlenen dosya=$duzenlenen  yeni dosya=$eklenen  duzenlenen dosyalar=$(diff -rq "$k" "$k-yeni" | grep '^Files' | sed 's|.*/\([a-z.]*\.mjs\) and.*|\1|' | tr '\n' ' ')"
done
dogrudan: duzenlenen dosya=1  yeni dosya=1  duzenlenen dosyalar=teslimat.mjs
gozlemci: duzenlenen dosya=1  yeni dosya=1  duzenlenen dosyalar=kur.mjs

Düzenlenen dosya sayısı ikisinde de bir. Kazanç sayıda değil, hangi dosya olduğundadır: doğrudan düzende alan nesnesi, gözlemci düzeninde bileşim kökü. Alan nesnesinin her yeni yan işte düzenlenmesi, o nesnenin değişim nedeni sayısını yan iş sayısı kadar artırır; bileşim köküne taşınan düzenleme ise o dosyanın zaten tek olan değişim nedenine denk gelir. Ölçü “kaç dosya” değil, “kaç değişim nedeni” olduğunda fark açığa çıkar: teslimat modülünün değişim nedeni 5’ten 1’e iner.

Özet

  • Gözlemci kalıbı durum değişimini bildiren nesnenin dinleyicileri tanımamasını sağlar; bu derste bildirim aynı süreçte ve aynı çağrı yığıtında gerçekleşir.
  • Teslimat modülünün giden bağ sayısı 4’ten 0’a, ithal kapanışı 5’ten 1’e indi; bağlar yok olmadı, bileşim köküne taşındı ve orada 5 bağ oldu.
  • İki düzen aynı davranışı taşıyor: durum değişiminde tetiklenen iş sayısı ikisinde de 4, beşinci iş eklendikten sonra ikisinde de 5 çıktı.
  • Beşinci iş eklendiğinde her iki düzende 1 dosya düzenlendi; fark düzenlenen dosyanın alan nesnesi mi bileşim kökü mü olduğudur, ölçüsü değişim nedeni sayısıdır.
  • Kalıbın bedeli: abonelik kurulmadığında durum değişimi sessizce tamamlandı ve tetiklenen iş sayısı 0 çıktı; doğrudan çağıran düzende bu yol yoktur.

Sonraki Adım

Gözlemci, olmuş bir şeyi haber veriyor: bildirimin gönderildiği anda durum çoktan değişmiştir ve geri dönüş yoktur. Kitaplığın iş akışında ise tersi isteniyor. Bir gönderiye indirim uygulanması, rotasının değiştirilmesi ve iptali operatör tarafından yapılıyor ve yanlış uygulanan bir işlemin geri alınması gerekiyor. Bu üç işlem bugün doğrudan alan değiştiren çağrılar; geri alma istendiğinde her çağrı yerinde tersinin elle yazılması gerekir. Sonraki ders istemi bir nesneye çevirip geri alınabilir işlem sayısını ve geri alma 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