---
title: Gözlemci
source: 'https://academia.sh/tr/kurslar/tasarim-kaliplari/gozlemci'
course: 'Tasarım Kalıpları'
language: tr
updated: '2026-08-17T18:10:38+00:00'
license: 'CC BY-SA 4.0'
---

# 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.

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 bildirimi**dir; 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.

```sh
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.

```js
// 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.

```js
// 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.

```js
// 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.

```js
// 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.

```js
// 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.

```js
// 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
```

```sh
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.
