---
title: 'Yorumlayıcı ve Boş Nesne'
source: 'https://academia.sh/tr/kurslar/tasarim-kaliplari/yorumlayici-ve-bos-nesne'
course: 'Tasarım Kalıpları'
language: tr
updated: '2026-08-17T18:10:38+00:00'
license: 'CC BY-SA 4.0'
---

# Yorumlayıcı ve Boş Nesne

Tarife kurallarının kod olarak yazılması ile küçük bir kural dilinin metinden yorumlanmasının karşılaştırılması: yeni kural eklendiğinde düzenlenen kod dosyası ve veri satırı sayısı, kural ağacının düğüm sayısı, geçersiz metnin yakalanması; ayrıca indirim yokluğunun boş değerle ve boş nesneyle temsilinin boş değer denetimi sayısı ve hata fırlatan çağrı yeri sayısıyla ölçülmesi.

Arabulucu bir sırayı, anımsatıcı bir kopyayı saklıyordu; ikisi de veri tutuyordu ama veri hâlâ
kodun içinde yazılıydı. Tarife kuralları tarafında bunun sınırına gelindi. Her yeni koşullu kural
bir kaynak değişikliği demek, oysa kuralları yazan kişi geliştirici değil tarife birimi.
Kuralın metin olarak yazılıp çalışma zamanında değerlendirilmesi isteniyor.

**Yorumlayıcı** (interpreter) küçük bir dilin her yapı taşını bir nesne yapar ve metni bu
nesnelerden oluşan bir ağaca çevirir; ağacın kökünden istenen değerlendirme, ağacı aşağı doğru
gezerek yapılır. Aynı bölümde ikinci bir soru var: bir gönderiye hiçbir indirim uygulanmadığında
sonuç ne olmalı. **Boş nesne** (null object) bu yokluğu, hiçbir şey yapmayan ama aynı sözleşmeyi
taşıyan bir nesneyle temsil eder. Ölçülecek sayılar yeni kural eklendiğinde düzenlenen kod
dosyası sayısı, kural ağacının düğüm sayısı, boş değer denetimi sayısı ve hata fırlatan çağrı
yeri sayısıdır.

## Kural Dili

Dil üç şeyden oluşur: bir alanın bir sayıyla karşılaştırılması, `ve`, `veya`. Bağlaçlar arasında
öncelik yoktur; soldan sağa değerlendirilir. Kurallar bir metin dosyasında durur — ad, oran,
koşul.

```sh
mkdir -p gomulu yorumlayici bosnesne

cat > kurallar.txt <<'SON'
hacim-indirimi 8 agirlik > 20
uzak-bolge 15 bolge = 3 ve agirlik < 5
degerli-gonderi 4 deger > 5000 veya hacim > 100
SON
```

```js
// veri.mjs — kurallarin denenecegi gonderiler
export const GONDERILER = [
  { kod: "GN-1", agirlik: 2, hacim: 8, bolge: 1, deger: 400 },
  { kod: "GN-2", agirlik: 25, hacim: 60, bolge: 2, deger: 900 },
  { kod: "GN-3", agirlik: 3, hacim: 20, bolge: 3, deger: 200 },
  { kod: "GN-4", agirlik: 6, hacim: 140, bolge: 2, deger: 8000 },
];
```

Karşılaştırma sürümünde aynı üç kural doğrudan kod olarak yazılmıştır.

```js
// gomulu/kurallar.mjs — kosullar kod olarak yazilmis, degisimi surum gerektirir
export const KURALLAR = [
  ["hacim-indirimi", 8, (g) => g.agirlik > 20],
  ["uzak-bolge", 15, (g) => g.bolge === 3 && g.agirlik < 5],
  ["degerli-gonderi", 4, (g) => g.deger > 5000 || g.hacim > 100],
];
```

Yorumlayıcı sürümünde her yapı taşı bir nesnedir. Yaprak düğüm bir karşılaştırma, iç düğüm bir
bağlaçtır; her düğüm kendi altındaki düğüm sayısını da taşır.

```js
// yorumlayici/dil.mjs — kural dilinin dugumleri ve ayristiricisi
const ALANLAR = ["agirlik", "hacim", "bolge", "deger"];
const ILISKILER = [">", "<", "="];

export const karsilastir = (alan, iliski, sayi) => ({
  tur: "karsilastir",
  dugumSayisi: 1,
  degerlendir: (g) => (iliski === ">" ? g[alan] > sayi : iliski === "<" ? g[alan] < sayi : g[alan] === sayi),
});

const ikili = (tur, birlestir) => (sol, sag) => ({
  tur,
  dugumSayisi: sol.dugumSayisi + sag.dugumSayisi + 1,
  degerlendir: (g) => birlestir(sol.degerlendir(g), sag.degerlendir(g)),
});

export const ve = ikili("ve", (a, b) => a && b);
export const veya = ikili("veya", (a, b) => a || b);

function terim([alan, iliski, sayi]) {
  if (ALANLAR.includes(alan) === false) throw new SyntaxError(`bilinmeyen alan: ${alan}`);
  if (ILISKILER.includes(iliski) === false) throw new SyntaxError(`bilinmeyen iliski: ${iliski}`);
  return karsilastir(alan, iliski, Number(sayi));
}

export function ayristir(metin) {
  const p = metin.trim().split(/\s+/);
  let agac = terim(p.splice(0, 3));
  while (p.length > 0) {
    const baglac = p.shift();
    if (baglac !== "ve" && baglac !== "veya") throw new SyntaxError(`bilinmeyen baglac: ${baglac}`);
    agac = (baglac === "ve" ? ve : veya)(agac, terim(p.splice(0, 3)));
  }
  return agac;
}

export const yukle = (icerik) =>
  icerik
    .split("\n")
    .filter((s) => s.trim().length > 0)
    .map((s) => {
      const [ad, oran, ...kosul] = s.trim().split(/\s+/);
      return [ad, Number(oran), ayristir(kosul.join(" "))];
    });
```

## İki Sürümün Eşitliği

```js
// kosum.mjs — kod olarak yazilmis kurallarla metinden yorumlanan kurallari karsilastirir
import { readFileSync } from "node:fs";
import { GONDERILER } from "./veri.mjs";
import { KURALLAR } from "./gomulu/kurallar.mjs";
import { yukle } from "./yorumlayici/dil.mjs";

const metinKurallar = yukle(readFileSync("kurallar.txt", "utf8"));

const bul = (kurallar, g, calistir) => {
  for (const [ad, oran, kosul] of kurallar) if (calistir(kosul, g)) return `${ad} %${oran}`;
  return "indirim yok";
};

let sapma = 0;
for (const g of GONDERILER) {
  const a = bul(KURALLAR, g, (k, x) => k(x));
  const b = bul(metinKurallar, g, (k, x) => k.degerlendir(x));
  if (a !== b) sapma += 1;
  console.log(`${g.kod} gomulu=${a.padEnd(18)} yorumlayici=${b}`);
}
console.log(`iki surum arasindaki sapma sayisi = ${sapma}`);
console.log(`kural agaclarinin dugum sayisi = ${metinKurallar.map(([, , k]) => k.dugumSayisi).join(" ")}`);
```

```
GN-1 gomulu=indirim yok        yorumlayici=indirim yok
GN-2 gomulu=hacim-indirimi %8  yorumlayici=hacim-indirimi %8
GN-3 gomulu=uzak-bolge %15     yorumlayici=uzak-bolge %15
GN-4 gomulu=degerli-gonderi %4 yorumlayici=degerli-gonderi %4
iki surum arasindaki sapma sayisi = 0
kural agaclarinin dugum sayisi = 1 3 3
```

Sapma sıfır. Son satır bedelin ilk kalemini veriyor: kod sürümünde bir kural bir işlev
çağrısıdır, yorumlayıcı sürümünde bir, üç ve üç düğümlük ağaçlardır. Tek bağlaçlı bir kuralı
değerlendirmek üç nesne ziyareti, iki `degerlendir` çağrısı ve bir birleştirme demektir.

## Dördüncü Kural Eklendiğinde

Tarife birimi yeni bir kural istiyor: birinci bölgedeki beş kilogramdan hafif gönderilere yüzde
on iki, diğer kuralların önünde. İki sürümde de eklenir ve düzenlenen dosyalar sayılır.

```js
// yenikural.mjs — dorduncu kural iki surume de eklenir, duzenlenen dosya sayilir
import { appendFileSync, copyFileSync, cpSync, readFileSync, writeFileSync } from "node:fs";
import { GONDERILER } from "./veri.mjs";
import { yukle } from "./yorumlayici/dil.mjs";

cpSync("gomulu", "gomulu-yeni", { recursive: true });
copyFileSync("kurallar.txt", "kurallar-yeni.txt");

writeFileSync(
  "gomulu-yeni/kurallar.mjs",
  readFileSync("gomulu/kurallar.mjs", "utf8").replace(
    '  ["hacim-indirimi", 8, (g) => g.agirlik > 20],',
    '  ["ekspres-kampanya", 12, (g) => g.agirlik < 5 && g.bolge === 1],\n  ["hacim-indirimi", 8, (g) => g.agirlik > 20],',
  ),
);
appendFileSync("kurallar-yeni.txt", "ekspres-kampanya 12 agirlik < 5 ve bolge = 1\n");

const kodFarki = (a, b) => (readFileSync(a, "utf8") === readFileSync(b, "utf8") ? 0 : 1);
console.log(`duzenlenen .mjs dosyasi: gomulu=${kodFarki("gomulu/kurallar.mjs", "gomulu-yeni/kurallar.mjs")}  yorumlayici=0`);
console.log(`duzenlenen veri satiri : gomulu=0  yorumlayici=1`);

const yeniMetin = yukle(readFileSync("kurallar-yeni.txt", "utf8"));
const { KURALLAR } = await import("./gomulu-yeni/kurallar.mjs");
const bul = (kurallar, g, calistir) => {
  for (const [ad, oran, kosul] of kurallar) if (calistir(kosul, g)) return `${ad} %${oran}`;
  return "indirim yok";
};
let sapma = 0;
for (const g of GONDERILER) {
  if (bul(KURALLAR, g, (k, x) => k(x)) !== bul(yeniMetin, g, (k, x) => k.degerlendir(x))) sapma += 1;
}
console.log(`GN-1 yeni kuralla: ${bul(yeniMetin, GONDERILER[0], (k, x) => k.degerlendir(x))}`);
console.log(`dort kuralda sapma = ${sapma}`);

for (const hatali of ["agirlik >> 20", "hacim > 5 ya da deger > 10", "sicaklik > 4"]) {
  try {
    yukle(`x 5 ${hatali}`);
    console.log(`"${hatali}" kabul edildi`);
  } catch (e) {
    console.log(`"${hatali}" -> ${e.constructor.name}: ${e.message}`);
  }
}
```

```
duzenlenen .mjs dosyasi: gomulu=1  yorumlayici=0
duzenlenen veri satiri : gomulu=0  yorumlayici=1
GN-1 yeni kuralla: ekspres-kampanya %12
dort kuralda sapma = 0
"agirlik >> 20" -> SyntaxError: bilinmeyen iliski: >>
"hacim > 5 ya da deger > 10" -> SyntaxError: bilinmeyen baglac: ya
"sicaklik > 4" -> SyntaxError: bilinmeyen alan: sicaklik
```

Bir koda karşı sıfır kod. Yorumlayıcı sürümünde eklenen şey bir metin satırıdır; kaynak
değişmediği için yeni bir yayın gerekmez ve kuralı yazan kişi geliştirici olmak zorunda değildir.
Kazanç bu iki cümlededir, satır sayısında değil.

Son üç satır bedelin ikinci kalemidir. Kod sürümünde geçersiz bir koşul yazmak sözdizimi
hatasıdır ve dosya yüklenirken ortaya çıkar; metin sürümünde geçersiz koşul ancak ayrıştırıcı onu
okuduğunda anlaşılır. Burada ayrıştırıcı üç hata sınıfını da yakalıyor — bilinmeyen ilişki,
bilinmeyen bağlaç, bilinmeyen alan — fakat bu yakalama kendiliğinden gelmedi: alan ve ilişki
listeleri elle yazıldı. Yorumlayıcı kalıbının bedeli, dilin kabul ettiği her şeyin ayrıştırıcıda
açıkça sayılması zorunluluğudur. Dil büyüdükçe ayrıştırıcı büyür; on alanlı ve beş bağlaçlı bir
kural dili artık küçük bir dil değildir ve o noktada kalıbın yerini gerçek bir dil işleme
yaklaşımı alır.

## Yokluğun Temsili

İkinci soru indirim bulunamadığında ne döneceğidir. Birinci tasarımda boş değer döner ve her
çağrı yeri onu denetlemek zorundadır — üçüncü çağrı yerinde denetim atlanmıştır.

```js
// gomulu/uygula.mjs — indirim yoksa bos deger doner; uc cagri yeri bunu denetlemek zorunda
export const indirimBul = (kurallar, g) => {
  for (const [ad, oran, kosul] of kurallar) if (kosul(g)) return { ad, oran };
  return null;
};

export const ucret = (i, taban) => (i === null ? taban : taban - Math.round((taban * i.oran) / 100));
export const etiket = (i) => (i === null ? "indirim yok" : `${i.ad} %${i.oran}`);
export const rapor = (i) => i.oran;
```

İkinci tasarımda yokluk da bir nesnedir: aynı sözleşmeyi taşır, oranı sıfırdır.

```js
// bosnesne/uygula.mjs — indirim yoksa hicbir sey yapmayan nesne doner
export const BOS_INDIRIM = { ad: "indirim yok", oran: 0 };

export const indirimBul = (kurallar, g) => {
  for (const [ad, oran, kosul] of kurallar) if (kosul(g)) return { ad, oran };
  return BOS_INDIRIM;
};

export const ucret = (i, taban) => taban - Math.round((taban * i.oran) / 100);
export const etiket = (i) => (i.oran === 0 ? i.ad : `${i.ad} %${i.oran}`);
export const rapor = (i) => i.oran;
```

```js
// bos.mjs — bos deger ile bos nesne iki tasarimda karsilastirilir
import { readFileSync } from "node:fs";
import { GONDERILER } from "./veri.mjs";
import { KURALLAR } from "./gomulu/kurallar.mjs";
import * as bosDeger from "./gomulu/uygula.mjs";
import * as bosNesne from "./bosnesne/uygula.mjs";

const TABAN = 10000;
let patlayan = { bosDeger: 0, bosNesne: 0 };
let sapma = 0;

for (const g of GONDERILER) {
  const satir = [];
  for (const [ad, m] of [["bosDeger", bosDeger], ["bosNesne", bosNesne]]) {
    const i = m.indirimBul(KURALLAR, g);
    let r;
    try {
      r = `${m.ucret(i, TABAN)}|${m.etiket(i)}|${m.rapor(i)}`;
    } catch (e) {
      patlayan[ad] += 1;
      r = `${m.ucret(i, TABAN)}|${m.etiket(i)}|${e.constructor.name}`;
    }
    satir.push(r);
  }
  if (satir[0] !== satir[1]) sapma += 1;
  console.log(`${g.kod} bosDeger=${satir[0].padEnd(34)} bosNesne=${satir[1]}`);
}

const denetim = (yol) => (readFileSync(yol, "utf8").match(/=== null|!== null/g) ?? []).length;
console.log(`bos deger denetimi: bosDeger=${denetim("gomulu/uygula.mjs")}  bosNesne=${denetim("bosnesne/uygula.mjs")}`);
console.log(`hata firlatan cagri yeri: bosDeger=${patlayan.bosDeger}  bosNesne=${patlayan.bosNesne}`);
console.log(`iki tasarim arasindaki sapma = ${sapma}`);
```

```
GN-1 bosDeger=10000|indirim yok|TypeError        bosNesne=10000|indirim yok|0
GN-2 bosDeger=9200|hacim-indirimi %8|8           bosNesne=9200|hacim-indirimi %8|8
GN-3 bosDeger=8500|uzak-bolge %15|15             bosNesne=8500|uzak-bolge %15|15
GN-4 bosDeger=9600|degerli-gonderi %4|4          bosNesne=9600|degerli-gonderi %4|4
bos deger denetimi: bosDeger=2  bosNesne=0
hata firlatan cagri yeri: bosDeger=1  bosNesne=0
iki tasarim arasindaki sapma = 1
```

İndirim bulunan üç gönderide iki tasarım aynı üçlüyü veriyor; sapma yalnız indirimsiz gönderide
ve orada boş değer tasarımı hata fırlatıyor. Denetim sayısı ikiye karşı sıfır: boş nesne
tasarımında yazılacak denetim yok, bu yüzden atlanacak denetim de yok.

Boş nesnenin bedeli sorumluluk zinciri dersinde işaret edilen belirsizliğin geri gelmesidir:
`BOS_INDIRIM` dönmek, "kural bulunamadı" ile "oranı sıfır olan bir kural bulundu" arasındaki
farkı silmiştir. Etiketin metni bu farkı taşıyor ama sayı taşımıyor. Kalıp bu yüzden yalnız
yokluğun **davranışsız** olduğu yerlerde uygulanır; yokluğun bir karar gerektirdiği yerde açık
sonuç türü boş nesneden üstündür.

## Özet

- Yorumlayıcı küçük bir dilin her yapı taşını bir nesne yapar ve metni düğüm ağacına çevirir;
  kod olarak yazılmış kurallarla aynı sonucu üretti, sapma 0 çıktı.
- Dördüncü kural eklendiğinde kod sürümünde 1 kaynak dosyası düzenlendi, yorumlayıcı sürümünde
  0 kaynak dosyası ve 1 veri satırı; iki sürüm dört kuralda da aynı sonucu verdi.
- Bedel: bir işlev çağrısı yerine 3 düğümlük ağaç ziyaret edilir ve dilin kabul ettiği her alan,
  ilişki ve bağlaç ayrıştırıcıda elle sayılmak zorundadır.
- Boş nesne yokluğu aynı sözleşmeyi taşıyan bir nesneyle temsil eder; boş değer denetimi sayısı
  2'den 0'a indi ve atlanmış denetim yüzünden hata fırlatan çağrı yeri sayısı 1'den 0'a indi.
- Boş nesnenin bedeli "kural bulunamadı" ile "oranı sıfır olan kural" arasındaki farkın
  silinmesidir; yokluk bir karar gerektiriyorsa açık sonuç türü tercih edilir.

## Sonraki Adım

Dokuz derste dokuz kalıbın kazancı da bedeli de sayıldı ve ölçümlerin yapıldığı ölçeklerde —
strateji dört tarifede, zincir yedi kuralda, arabulucu dört alanda — kazanç her seferinde bedeli
aştı. Sayılar bir eşiğin varlığını gösteriyor, ama eşiğin altında ne olduğu henüz ölçülmedi.
Tek gerçekleştirimi olan bir strateji, tek gözlemcisi olan bir olay, tek adımı olan bir şablon,
iki durumlu bir durum makinesi ve tek işleyicili bir zincir aynı ölçülerle sınandığında ne
çıkar? Sonraki ders dokuz derste kullanılan ölçülerin aynısını bu kez kalıbın aleyhine koşturur:
eklenen dosya sayısı, dolaylılık basamağı ve izlenmesi gereken çağrı sayısı, karşılığında sıfır
kazançla.
