---
title: 'Tasarım Kalıpları'
source: 'https://academia.sh/tr/kurslar/tasarim-kaliplari'
language: tr
updated: '2026-08-17T04:42:36+00:00'
license: 'CC BY-SA 4.0'
lessons: 30
outcomes:
    - 'Klasik kalıpları çözdükleri probleme göre sınıflandırır'
    - 'Bir kalıbı gereksiz karmaşıklık üretmeden uygular'
    - 'Kurumsal uygulama kalıplarını veri erişim tasarımında kullanır'
---

# Tasarım Kalıpları

Aynı tasarım probleminin defalarca aynı biçimde çözülmesinden doğan adlandırılmış çözümler: yaratım sorumluluğunun soyutlanması, nesnelerin bileşimle genişletilmesi, davranışın çalışma zamanında değiştirilmesi ve kurumsal uygulamalarda iş mantığı ile kalıcılık arasındaki sınırın kalıplarla kurulması; her kalıbın kazancı ve bedeli tasarım ölçütleriyle sayılarak karşılaştırılır.

## Yaratımsal Kalıplar

Bir nesnenin nasıl kurulacağı bilgisinin onu kullanan koddan ayrılması: kalıp kavramının problem–çözüm–sonuçlar üçlüsüyle tanımlanması, yaratımın fabrika yöntemi ve soyut fabrikayla soyutlanması, çok parametreli kurulumun adım adım yürütülmesi, kopyalayarak yaratma, tekil nesnenin küresel durum açısından eleştirisi ve yaratım sorumluluğunun bağımlılık enjeksiyonuyla dışarı alınması.

### Kalıp Kavramı

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/kalip-kavrami

Bir kalıbın problem, çözüm ve sonuçlar üçlüsü olarak tanımlanması: gönderi kitaplığında iki ayrı dersten tanıdık olan kayıt çözümünün taşıyıcı seçimine uygulanması, aynı çıktıyı veren iki sürümün dosya sayısı, ithal kenarı sayısı, tür adının geçtiği yer sayısı ve en uzun ithal yolu ile ölçülmesi, kazancın ve bedelin ayrı ayrı sayılması.

Tasarım İlkeleri kursu bir ölçüt kümesiyle kapandı: sınır dar olsun, bağ kararlı yöne aksın,
birlikte değişenler bir arada dursun. Bu cümlelerin hiçbiri bir çözüm vermez. Bir modülün
açık–kapalı ilkesini karşılamadığı ölçülebilir, ama ilke o modülün yerine ne yazılacağını
söylemez. Kararı veren şey, aynı problemin daha önce çözülmüş olmasıdır.

Aynı problem farklı kod tabanlarında defalarca aynı biçimde çözülmüşse, o çözümün bir adı,
bilinen bir uygulanış biçimi ve bilinen sonuçları vardır. **Kalıp** (pattern) budur: bir kod
parçası değil, tekrar eden bir problemin adlandırılmış çözümü ve o çözümün getirdiği
sonuçların listesi. Bu ders üçlünün parçalarını tanımlar, gönderi ücretlendirme kitaplığında
daha önce iki kez bulunmuş bir çözümü taşıyıcı seçimine uygular ve sonuçlarını sayar.

#### Aynı Çözümün İkinci Kez Bulunması

Temiz Kod kursunun Doğru Yapıyı Seçmek dersinde uzun bir koşul zinciri bir tabloya taşınmıştı;
tablo, tür adını davranışa eşleyen bir **kayıt** idi. Tasarım İlkeleri kursunun Açık–Kapalı
İlkesi dersinde aynı yapı yeniden çıktı: tarife türü `switch` ile çözülmek yerine tarifeler
kendilerini bir kayda yazdı, hesap modülü tür adlarını hiç bilmedi.

İki dersin problemi aynı değildi. Birincisi bir gövdenin karar noktası sayısını düşürmek
istiyordu, ikincisi yeni bir tür eklendiğinde var olan bir dosyanın düzenlenmesini
engellemek. Buna karşın bulunan çözümün yapısı harfi harfine aynıydı: bir eşleme tablosu,
tabloya yazan bir işlev, tablodan okuyan bir işlev ve tabloyu dolduran tek bir bileşim kökü.
Aynı çözümün ayrı baskılar altında ikinci kez bulunması, o çözümün adlandırılmayı hak
ettiğinin işaretidir.

#### Üçlünün Parçaları

Bir kalıp anlatısı üç bölümden oluşur; her bölüm ayrı bir soruya cevap verir.

| Parça | Sorusu | Kayıt çözümünde karşılığı |
|---|---|---|
| Problem | Hangi baskı altında ortaya çıkar | Tür sayısı artıyor ve her yeni tür var olan dosyaları düzenletiyor |
| Çözüm | Hangi parçalar hangi ilişkiyle durur | Ad–davranış tablosu, yazan işlev, okuyan işlev, dolduran bileşim kökü |
| Sonuçlar | Neyi iyileştirir, neyi kötüleştirir | Tür adının geçtiği yer sayısı düşer; dosya ve ithal kenarı sayısı artar |

Üçlünün önünde bir **bağlam** cümlesi durur: kalıbın uygulanabilir olduğu koşul. Kayıt
çözümünün bağlamı, türlerin çalışma zamanında ada göre seçilmesidir. Türler derleme
zamanında belliyse ve seçim hiç yapılmıyorsa problem yoktur, dolayısıyla kalıp da yoktur.

Üç parçadan en çok atlananı sonuçlardır. Bir kalıbı "uygulamak" onun yapısını kurmak değil,
sonuçlarını kabul etmektir; bu yüzden sonuçlar sayılmadan kalıp seçimi yapılamaz.

#### Taşıyıcı Seçiminin İki Sürümü

Kitaplıkta yeni bir problem var: bir gönderi için birden çok taşıyıcı teklifi üretilecek. Her
taşıyıcının kendi ücreti ve kendi teslim süresi var. Birinci sürümde taşıyıcı adı iki ayrı
`switch` ile çözülür.

```js
// sec/tasiyici.mjs — tasiyici adi iki ayri switch ile cozulur
export function ucret(ad, gonderi) {
  switch (ad) {
    case "yurtici":
      return 3900 + Math.ceil(gonderi.agirlik) * 900;
    case "hizli":
      return 6400 + Math.ceil(gonderi.agirlik) * 1500;
    default:
      throw new RangeError(`bilinmeyen tasiyici: ${ad}`);
  }
}

export function sureGun(ad) {
  switch (ad) {
    case "yurtici":
      return 3;
    case "hizli":
      return 1;
    default:
      throw new RangeError(`bilinmeyen tasiyici: ${ad}`);
  }
}
```

```js
// sec/teklif.mjs — teklif satiri; tasiyici adlarini kendisi de sayar
import { ucret, sureGun } from "./tasiyici.mjs";

export const ADLAR = ["yurtici", "hizli"];
export const satir = (ad, g) => `${ad}: ${ucret(ad, g)} kurus / ${sureGun(ad)} gun`;
```

```js
// sec/kur.mjs — ornek gonderi icin butun tasiyici tekliflerini yazar
import { ADLAR, satir } from "./teklif.mjs";

export const GONDERI = { agirlik: 2.4, adres: "34100" };
for (const ad of ADLAR) console.log(satir(ad, GONDERI));
```

```sh
node sec/kur.mjs
```

```
yurtici: 6600 kurus / 3 gun
hizli: 10900 kurus / 1 gun
```

İkinci sürüm kayıt çözümünü uygular. Her taşıyıcı kendi dosyasında bir nesne olarak durur ve
kendini kayda yazar.

```js
// kayit/kayit.mjs — tasiyici kaydi: her tasiyici kendini bu tabloya yazar
const TASIYICILAR = new Map();

export function kaydet(tasiyici) {
  TASIYICILAR.set(tasiyici.ad, tasiyici);
}

export const adlar = () => [...TASIYICILAR.keys()];
export function tasiyici(ad) {
  const t = TASIYICILAR.get(ad);
  if (t === undefined) throw new RangeError(`bilinmeyen tasiyici: ${ad}`);
  return t;
}
```

```js
// kayit/tasiyicilar/yurtici.mjs — yurtici tasiyici
import { kaydet } from "../kayit.mjs";

kaydet({
  ad: "yurtici",
  ucret: (g) => 3900 + Math.ceil(g.agirlik) * 900,
  sureGun: () => 3,
});
```

```js
// kayit/tasiyicilar/hizli.mjs — hizli tasiyici
import { kaydet } from "../kayit.mjs";

kaydet({
  ad: "hizli",
  ucret: (g) => 6400 + Math.ceil(g.agirlik) * 1500,
  sureGun: () => 1,
});
```

```js
// kayit/teklif.mjs — teklif satiri; hicbir tasiyici adi gecmez
import { tasiyici } from "./kayit.mjs";

export const satir = (ad, g) => {
  const t = tasiyici(ad);
  return `${ad}: ${t.ucret(g)} kurus / ${t.sureGun()} gun`;
};
```

```js
// kayit/kur.mjs — bilesim koku: hangi tasiyicilarin yuklendigini yalniz burasi bilir
import "./tasiyicilar/yurtici.mjs";
import "./tasiyicilar/hizli.mjs";
import { adlar } from "./kayit.mjs";
import { satir } from "./teklif.mjs";

export const GONDERI = { agirlik: 2.4, adres: "34100" };
for (const ad of adlar()) console.log(satir(ad, GONDERI));
```

```sh
node kayit/kur.mjs
```

```
yurtici: 6600 kurus / 3 gun
hizli: 10900 kurus / 1 gun
```

Çıktılar aynı. Fark yalnızca sonuçlardadır ve sonuçlar sayılabilir.

#### Sonuçların Sayılması

Aşağıdaki ölçer bir dizindeki modülleri okur ve dört sayı verir: dosya sayısı, ithal kenarı
sayısı, verilen tür adlarının dizgi olarak geçtiği yer sayısı ve ithal çizgesindeki en uzun
yolun uzunluğu. İlk iki sayı kalıbın bedelini, üçüncüsü kazancını, dördüncüsü dolaylılık
basamağını ölçer.

```js
// olc.mjs — bir tasarimin dosya, ithal kenari, tur adi ve dolaylilik olculeri
import { readdirSync, readFileSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";

const kok = process.argv[2];
const TURLER = process.argv.slice(3);

const topla = (d) =>
  readdirSync(d, { withFileTypes: true }).flatMap((e) =>
    e.isDirectory() ? topla(join(d, e.name)) : e.name.endsWith(".mjs") ? [join(d, e.name)] : []);

const metin = new Map(topla(kok).sort().map((y) => [relative(kok, y), readFileSync(y, "utf8")]));

const kenar = new Map();
for (const [ad, m] of metin) {
  kenar.set(ad, [...m.matchAll(/(?:from|import)\s*["'](\.[^"']+)["']/g)]
    .map((x) => relative(kok, resolve(dirname(join(kok, ad)), x[1]))));
}
const kenarSayisi = [...kenar.values()].reduce((t, h) => t + h.length, 0);

let turYeri = 0;
for (const [, m] of metin)
  for (const t of TURLER) turYeri += [...m.matchAll(new RegExp(`"${t}"`, "g"))].length;

const derinlik = (ad, gorulen = new Set()) => {
  const alt = (kenar.get(ad) ?? []).filter((h) => !gorulen.has(h));
  return alt.length === 0 ? 0 : 1 + Math.max(...alt.map((h) => derinlik(h, new Set([...gorulen, ad]))));
};
const enUzun = Math.max(...[...kenar.keys()].map((a) => derinlik(a)));

console.log(`${kok.padEnd(6)} dosya=${metin.size} ithal-kenari=${kenarSayisi} ` +
  `tur-adi-gecen-yer=${turYeri} en-uzun-ithal-yolu=${enUzun}`);
```

```sh
node olc.mjs sec yurtici hizli
node olc.mjs kayit yurtici hizli
```

```
sec    dosya=3 ithal-kenari=2 tur-adi-gecen-yer=6 en-uzun-ithal-yolu=2
kayit  dosya=5 ithal-kenari=7 tur-adi-gecen-yer=2 en-uzun-ithal-yolu=2
```

Kazanç üçüncü sayıda. Birinci sürümde iki taşıyıcı adı altı yerde geçiyor: her ad iki
`switch` içinde birer kez ve `ADLAR` dizisinde bir kez. Bu, Temiz Kod kursunda tanımlanan
bilgi yinelemesidir; taşıyıcı listesi üç ayrı yerde ayrı ayrı tutulur ve üçünün eşit
kalması hiçbir denetimle güvence altında değildir. İkinci sürümde her ad bir kez, yalnız
kendi dosyasında geçer.

Bedel ilk iki sayıda. Dosya sayısı üçten beşe, ithal kenarı sayısı ikiden yediye çıktı. İkinci
sayı, Bileşen Bağlaşım İlkeleri dersinde sayılan bağ sayısının aynısıdır: çizgeye beş kenar
eklendi ve bunların dördü tek bir dosyaya, bileşim köküne bağlıdır.

Dördüncü sayı kalıbın bedelinin nerede görünmediğini gösteriyor: en uzun ithal yolu iki
sürümde de 2. Yani dolaylılık, dosyalar arasındaki yol uzunluğunda değil, çalışma zamanındaki
çözümlemede artıyor. Birinci sürümde `ucret` çağrısının hangi gövdeye gittiği dosyayı okuyarak
bulunur; ikinci sürümde çağrının hedefi kayda hangi dosyaların yazdığına bağlıdır ve bu bilgi
yalnız bileşim kökünde durur. Tek bir ölçü kalıbın bedelini vermeye yetmez.

#### Problem Sınıflarına Göre Bölünme

Kalıplar çözdükleri problem türüne göre ayrılır ve bu kurs bölümlerini o ayrımdan alır.
**Yaratımsal kalıplar** bir nesnenin kim tarafından ve nasıl kurulacağı sorusunu çözer.
**Yapısal kalıplar** var olan nesnelerin nasıl birleştirileceğini düzenler. **Davranışsal
kalıplar** nesneler arasındaki sorumluluk ve iletişim dağılımını ele alır. **Kurumsal
uygulama kalıpları** iş mantığı ile kalıcılık arasındaki sınıra aittir; bunların bir kısmı
Backend Geliştirme müfredatının Veri Erişim Katmanı kursunda kurulmuştu ve burada kalıp
kataloğu düzeyinde yeniden ele alınır.

Kayıt çözümü bu sınıflamada yaratımsal tarafa düşer, çünkü çözdüğü şey davranışın kendisi
değil, davranışı taşıyan nesnenin nereden geleceğidir. Sonraki iki ders bu soruyu daha keskin
biçimde sorar.

#### Özet

- Kalıp bir kod parçası değil, tekrar eden bir problemin adlandırılmış çözümüdür; anlatısı
  problem, çözüm ve sonuçlar üçlüsünden oluşur ve önünde bir bağlam koşulu durur.
- Bir çözümün kalıp sayılması, aynı yapının ayrı baskılar altında yeniden bulunmasına
  bağlıdır; kayıt çözümü Temiz Kod ve Tasarım İlkeleri kurslarında iki ayrı problem için
  birbirinin aynı biçimde çıktı.
- Aynı teklif satırını üreten iki sürümde tür adının geçtiği yer sayısı 6'dan 2'ye düştü;
  bu kalıbın kazancıdır.
- Aynı karşılaştırmada dosya sayısı 3'ten 5'e, ithal kenarı sayısı 2'den 7'ye çıktı; bu
  kalıbın bedelidir ve kazançla aynı ölçü kümesinden okunur.
- En uzun ithal yolu iki sürümde de 2 kaldı: bir kalıbın dolaylılık bedeli tek bir ölçüyle
  görünmez, çağrı hedefinin çalışma zamanında çözülmesi ayrıca hesaba katılır.

#### Sonraki Adım

Kayıt sürümünde teklif modülü taşıyıcı adlarını bilmiyor, ama hangi adın hangi nesneye
karşılık geldiğini yine bir dizgi üzerinden soruyor ve nesnenin kendisini kayıt kuruyor.
Nesne kurma işi tek bir çağrıya sığmadığında — taşıyıcının bir ücret hesabı, bir rota
planlayıcısı ve bir etiket biçimi varsa — kayda yazılacak şeyin kendisi bir kurulum işlemine
dönüşür. Sonraki ders bu kurulumu bir yönteme çeker: alt tipin hangi nesneyi üreteceğine
kendisinin karar verdiği fabrika yöntemini tanımlar, birbirine uyması gereken nesneleri
birlikte üreten soyut fabrikayı ekler ve yeni bir taşıyıcı ailesi eklendiğinde düzenlenen
dosya ve satır sayısını iki sürümde sayar.

### Fabrika Yöntemi ve Soyut Fabrika

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/fabrika-yontemi-ve-soyut-fabrika

Nesne yaratımının bir yönteme ve bir aile üretecine çekilmesi: üretecin hangi tarifeyi kuracağına alt tipin karar verdiği fabrika yöntemi, birbirine uyması gereken tarife, rota ve etiket üçlüsünü birlikte üreten soyut fabrika, çağrı yerinde eşleştirme yapılan sürümde sessizce kabul edilen yanlış birleşimlerin sayılması ve yeni bir taşıyıcı ailesi eklendiğinde düzenlenen dosya sayısının karşılaştırılması.

Kayıt çözümü taşıyıcı adlarını tek bir yere topladı, ama kayda yazılan şeyin nasıl kurulduğu
sorusuna hiç dokunmadı. Bir taşıyıcının tek bir ücret işlevi olduğu sürece kurulum tek bir
nesne sabitidir. Taşıyıcının bir ücret tarifesi, bir rota planı ve bir etiket biçimi olduğunda
kurulum üç parçanın birlikte hazırlanmasına dönüşür ve bu üç parça birbirine uymak zorundadır.

Bu ders iki kalıbı tanımlar. **Fabrika yöntemi** (factory method) tek bir nesnenin yaratımını
bir yönteme çeker ve o yöntemi alt tipin karşılamasını bekler. **Soyut fabrika** (abstract
factory) birbirine uyması gereken bir nesne ailesini tek çağrıda birlikte üretir. İkisinin
problemi de yaratım kararının nereye yazıldığıdır; ölçüleri farklıdır.

#### Fabrika Yöntemi

Üreteç, teklif satırını hazırlayan tiptir. Hangi tarifenin kurulacağına kendisi karar
vermez: kurulumu `tarifeYap` yöntemine bırakır ve o yöntemi alt tipler kendi
gerçekleştirimiyle karşılar.

```js
// yontem/uretec.mjs — ureteci; hangi tarifenin kurulacagini alt tip belirler
export class TeklifUreteci {
  tarifeYap() {
    throw new TypeError(`${this.constructor.name} tarifeYap yontemini karsilamadi`);
  }

  teklif(gonderi) {
    const t = this.tarifeYap();
    return `${t.ad}: ${t.ucret(gonderi)} kurus`;
  }
}
```

```js
// yontem/kur.mjs — iki alt tip ayni yontemi kendi tarifesiyle karsilar
import { TeklifUreteci } from "./uretec.mjs";

class YurticiUreteci extends TeklifUreteci {
  tarifeYap() {
    return { ad: "yurtici", ucret: (g) => 3900 + Math.ceil(g.agirlik) * 900 };
  }
}

class YurtdisiUreteci extends TeklifUreteci {
  tarifeYap() {
    return { ad: "yurtdisi", ucret: (g) => 18400 + Math.ceil(g.agirlik) * 2600 };
  }
}

const G = { agirlik: 2.4, adres: "34100" };
for (const u of [new YurticiUreteci(), new YurtdisiUreteci()]) console.log(u.teklif(G));

class EksikUreteci extends TeklifUreteci {}
try {
  new EksikUreteci().teklif(G);
} catch (e) {
  console.log(`${e.constructor.name}: ${e.message}`);
}
```

```sh
node yontem/kur.mjs
```

```
yurtici: 6600 kurus
yurtdisi: 26200 kurus
TypeError: EksikUreteci tarifeYap yontemini karsilamadi
```

Üst tipin gövdesi hiçbir taşıyıcı adı taşımaz; taşıyıcı adları yalnız alt tiplerin
bulunduğu dosyada geçer. İki dosyada adı içeren satırlar sayıldığında ayrım görünür.

```sh
for d in yontem/uretec.mjs yontem/kur.mjs; do
  echo "$d: $(grep -c 'yurtici\|yurtdisi' $d)"
done
```

```
yontem/uretec.mjs: 0
yontem/kur.mjs: 2
```

Bu ölçü yeni değil: bir önceki derste kayıt sürümü için sayılan "tür adının geçtiği yer"
ölçüsünün aynısıdır ve aynı yönde iyileşir. Fabrika yöntemini kayıttan ayıran şey seçimin
nasıl yapıldığıdır. Kayıtta seçim bir dizgiyle, fabrika yönteminde hangi alt tipin örneğinin
elde tutulduğuyla yapılır. Üçüncü satır kalıbın kendi başına getirdiği tek yeni güvenceyi
gösteriyor: yöntemi karşılamayan bir alt tip ilk çağrıda hata verir, sessiz kalmaz.

#### Parçaların Çağrı Yerinde Eşleştirilmesi

Aile problemi fabrika yönteminin çözemediği yerde başlar. Aşağıdaki sürümde üç parça role göre
gruplanmıştır: bütün tarifeler bir dosyada, bütün rotalar bir dosyada, bütün etiketler bir
dosyada. Eşleştirmeyi çağıran yapar.

```js
// rol/tarife.mjs — role gore gruplanmis parcalar: iki ailenin tarifesi bir arada
export const yurticiTarife = { ad: "yurtici", ucret: (g) => 3900 + Math.ceil(g.agirlik) * 900 };
export const yurtdisiTarife = { ad: "yurtdisi", ucret: (g) => 18400 + Math.ceil(g.agirlik) * 2600 };
```

```js
// rol/rota.mjs — iki ailenin rotasi bir arada; gumruk noktasi yalniz yurtdisinda
export const yurticiRota = { duraklar: () => ["34", "06"], gumrukKodu: () => null };
export const yurtdisiRota = { duraklar: () => ["34", "GUM", "DE"], gumrukKodu: () => "TR34DE" };
```

```js
// rol/etiket.mjs — iki ailenin etiketi bir arada; her biri rotadan farkli sey ister
export const yurticiEtiket = (gonderi, rota) => {
  if (rota.gumrukKodu() !== null) throw new TypeError("yurtici etiketi gumruk noktasi tasimaz");
  return `TR ${rota.duraklar().join(">")} ${gonderi.agirlik}kg`;
};

export const yurtdisiEtiket = (gonderi, rota) => {
  const kod = rota.gumrukKodu();
  if (kod === null) throw new TypeError("yurtdisi etiketi gumruk kodu ister");
  return `INT ${kod} ${rota.duraklar().join(">")} ${gonderi.agirlik}kg`;
};
```

```js
// rol/teklif.mjs — uc parcayi cagiran verir; uyumu denetleyen kimse yok
export const teklif = (tarife, rota, etiket, gonderi) =>
  `${tarife.ucret(gonderi)} kurus  ${etiket(gonderi, rota)}`;
```

```js
// rol/kur.mjs — cagiran uc parcayi ada gore kendisi eslestirir
import { yurticiTarife, yurtdisiTarife } from "./tarife.mjs";
import { yurticiRota, yurtdisiRota } from "./rota.mjs";
import { yurticiEtiket, yurtdisiEtiket } from "./etiket.mjs";
import { teklif } from "./teklif.mjs";

export const GONDERI = { agirlik: 2.4, adres: "34100" };
console.log(teklif(yurticiTarife, yurticiRota, yurticiEtiket, GONDERI));
console.log(teklif(yurtdisiTarife, yurtdisiRota, yurtdisiEtiket, GONDERI));
```

```sh
node rol/kur.mjs
```

```
6600 kurus  TR 34>06 2.4kg
26200 kurus  INT TR34DE 34>GUM>DE 2.4kg
```

İki satır doğru. Sorun, `teklif` imzasının doğru olmayan çağrıları da kabul etmesidir: üç
parçanın her biri iki değerden birini alabildiği için sekiz farklı çağrı yazılabilir ve
bunların yalnız ikisi kastedilen çifttir.

#### Soyut Fabrika

İkinci sürümde gruplama role göre değil aileye göredir. Her aile bir işlevdir ve çağrıldığında
üç parçayı birlikte döndürür.

```js
// aile/yurtici.mjs — yurtici ailesi: birbirine uyan uc parca birlikte uretilir
export const yurticiAilesi = () => ({
  tarife: { ad: "yurtici", ucret: (g) => 3900 + Math.ceil(g.agirlik) * 900 },
  rota: { duraklar: () => ["34", "06"], gumrukKodu: () => null },
  etiket: (gonderi, rota) => {
    if (rota.gumrukKodu() !== null) throw new TypeError("yurtici etiketi gumruk noktasi tasimaz");
    return `TR ${rota.duraklar().join(">")} ${gonderi.agirlik}kg`;
  },
});
```

```js
// aile/yurtdisi.mjs — yurtdisi ailesi: gumruklu rota ve gumruk kodu isteyen etiket
export const yurtdisiAilesi = () => ({
  tarife: { ad: "yurtdisi", ucret: (g) => 18400 + Math.ceil(g.agirlik) * 2600 },
  rota: { duraklar: () => ["34", "GUM", "DE"], gumrukKodu: () => "TR34DE" },
  etiket: (gonderi, rota) => {
    const kod = rota.gumrukKodu();
    if (kod === null) throw new TypeError("yurtdisi etiketi gumruk kodu ister");
    return `INT ${kod} ${rota.duraklar().join(">")} ${gonderi.agirlik}kg`;
  },
});
```

```js
// aile/teklif.mjs — teklif uc parcayi tek bir aileden alir; eslestirme yapmaz
export const teklif = (aile, gonderi) => {
  const { tarife, rota, etiket } = aile();
  return `${tarife.ucret(gonderi)} kurus  ${etiket(gonderi, rota)}`;
};
```

```js
// aile/kur.mjs — bilesim koku: yalniz aile listesini bilir
import { yurticiAilesi } from "./yurtici.mjs";
import { yurtdisiAilesi } from "./yurtdisi.mjs";
import { teklif } from "./teklif.mjs";

export const AILELER = [yurticiAilesi, yurtdisiAilesi];
export const GONDERI = { agirlik: 2.4, adres: "34100" };
for (const a of AILELER) console.log(teklif(a, GONDERI));
```

```sh
node aile/kur.mjs
```

```
6600 kurus  TR 34>06 2.4kg
26200 kurus  INT TR34DE 34>GUM>DE 2.4kg
```

Aynı iki satır. Fark `teklif` imzasındadır: dört parametre yerine iki parametre alıyor ve
parçalar arası eşleştirme çağıranın elinden çıkmıştır.

#### Eşleştirmelerin Sayılması

Aşağıdaki ölçer iki tasarımda yazılabilen bütün çağrıları üretir, her birini çalıştırır ve
sonucu üçe ayırır: kastedilen satırı verenler, hata atanlar ve hata atmadığı hâlde kastedilen
satırı vermeyenler. Üçüncü sınıf, sessizce kabul edilen yanlış çağrılardır.

```js
// birlesim.mjs — iki tasarimda kurulabilen butun eslestirmeleri dener ve siniflar
import { yurticiTarife, yurtdisiTarife } from "./rol/tarife.mjs";
import { yurticiRota, yurtdisiRota } from "./rol/rota.mjs";
import { yurticiEtiket, yurtdisiEtiket } from "./rol/etiket.mjs";
import { teklif as rolTeklif } from "./rol/teklif.mjs";
import { yurticiAilesi } from "./aile/yurtici.mjs";
import { yurtdisiAilesi } from "./aile/yurtdisi.mjs";
import { teklif as aileTeklif } from "./aile/teklif.mjs";

const G = { agirlik: 2.4, adres: "34100" };
const dene = (f) => { try { return f(); } catch { return null; } };
const DOGRU = new Set([aileTeklif(yurticiAilesi, G), aileTeklif(yurtdisiAilesi, G)]);

function say(etiket, satirlar) {
  const hata = satirlar.filter((s) => s === null).length;
  const dogru = satirlar.filter((s) => s !== null && DOGRU.has(s)).length;
  const sessiz = satirlar.length - hata - dogru;
  console.log(`${etiket} birlesim=${satirlar.length} dogru=${dogru} ` +
    `sessiz-yanlis=${sessiz} hata=${hata}`);
}

say("rol ", [yurticiTarife, yurtdisiTarife].flatMap((t) =>
  [yurticiRota, yurtdisiRota].flatMap((r) =>
    [yurticiEtiket, yurtdisiEtiket].map((e) => dene(() => rolTeklif(t, r, e, G))))));
say("aile", [yurticiAilesi, yurtdisiAilesi].map((a) => dene(() => aileTeklif(a, G))));
```

```sh
node birlesim.mjs
```

```
rol  birlesim=8 dogru=2 sessiz-yanlis=2 hata=4
aile birlesim=2 dogru=2 sessiz-yanlis=0 hata=0
```

Birinci sürümde yazılabilen sekiz çağrının dördü hata atıyor, çünkü etiket rotadan gümrük
kodu istiyor ya da gümrük noktası bulunmasını yasaklıyor. Kalan dördün ikisi kastedilen
çifttir; diğer ikisi hata atmaz ve yanlış ücretle geçerli görünen bir etiket üretir. Yurtiçi
tarifesiyle yurtdışı rotası eşleştirildiğinde çıkan satır 6600 kuruş ile bir gümrük etiketi
taşır ve hiçbir denetim bunu yakalamaz.

İkinci sürümde yazılabilen çağrı sayısı sekizden ikiye düşüyor. Kazanç hata yakalamada değil,
yanlış çağrının **yazılamaz** olmasındadır: `teklif` artık parça almadığı için eşleştirme
diye bir işlem kalmamıştır. Soyut fabrikanın ölçülebilir katkısı budur ve sayısı 2'den 0'a
inen sessiz yanlışlardır.

#### Yeni Ailenin Maliyeti

İkinci ölçü Tasarım İlkeleri kursundan gelir: yeni bir tür eklendiğinde düzenlenen var olan
dosya ve eklenen satır sayısı. Yeni aile bir hava aktarmalı ekspres taşıyıcıdır; iki ağaç
kopyalanır ve aynı gereksinim ikisine de uygulanır.

```sh
cp -r rol rol-yeni
cp -r aile aile-yeni

cat >> rol-yeni/tarife.mjs <<'SON'
export const ekspresTarife = { ad: "ekspres", ucret: (g) => 24600 + Math.ceil(g.agirlik) * 4100 };
SON
cat >> rol-yeni/rota.mjs <<'SON'
export const ekspresRota = { duraklar: () => ["34", "GUM", "FRA"], gumrukKodu: () => "TR34FR" };
SON
cat >> rol-yeni/etiket.mjs <<'SON'
export const ekspresEtiket = (gonderi, rota) => {
  const kod = rota.gumrukKodu();
  if (kod === null) throw new TypeError("ekspres etiketi gumruk kodu ister");
  return `EXP ${kod} ${rota.duraklar().join(">")} ${gonderi.agirlik}kg`;
};
SON
cat >> rol-yeni/kur.mjs <<'SON'
import { ekspresTarife } from "./tarife.mjs";
import { ekspresRota } from "./rota.mjs";
import { ekspresEtiket } from "./etiket.mjs";
console.log(teklif(ekspresTarife, ekspresRota, ekspresEtiket, GONDERI));
SON

cat > aile-yeni/ekspres.mjs <<'SON'
// aile/ekspres.mjs — ucuncu aile: uc parcasi da bu dosyada
export const ekspresAilesi = () => ({
  tarife: { ad: "ekspres", ucret: (g) => 24600 + Math.ceil(g.agirlik) * 4100 },
  rota: { duraklar: () => ["34", "GUM", "FRA"], gumrukKodu: () => "TR34FR" },
  etiket: (gonderi, rota) => {
    const kod = rota.gumrukKodu();
    if (kod === null) throw new TypeError("ekspres etiketi gumruk kodu ister");
    return `EXP ${kod} ${rota.duraklar().join(">")} ${gonderi.agirlik}kg`;
  },
});
SON
cat >> aile-yeni/kur.mjs <<'SON'
import { ekspresAilesi } from "./ekspres.mjs";
console.log(teklif(ekspresAilesi, GONDERI));
SON

node rol-yeni/kur.mjs
node aile-yeni/kur.mjs

olc() {
  echo "$1 -> $2"
  echo "  duzenlenen var olan dosya = $(diff -rq $1 $2 | grep -c '^Files ')"
  echo "  yeni dosya                = $(diff -rq $1 $2 | grep -c '^Only in ')"
  echo "  eklenen satir             = $(diff -rN $1 $2 | grep '^>' | grep -cvE '^> *(//|$)')"
}
olc rol rol-yeni
olc aile aile-yeni
```

```
6600 kurus  TR 34>06 2.4kg
26200 kurus  INT TR34DE 34>GUM>DE 2.4kg
36900 kurus  EXP TR34FR 34>GUM>FRA 2.4kg
6600 kurus  TR 34>06 2.4kg
26200 kurus  INT TR34DE 34>GUM>DE 2.4kg
36900 kurus  EXP TR34FR 34>GUM>FRA 2.4kg
rol -> rol-yeni
  duzenlenen var olan dosya = 4
  yeni dosya                = 0
  eklenen satir             = 11
aile -> aile-yeni
  duzenlenen var olan dosya = 1
  yeni dosya                = 1
  eklenen satir             = 11
```

Eklenen satır sayısı ikisinde de 11; yazılan kod miktarı aynı. Düzenlenen var olan dosya
sayısı dörtten bire indi ve kalan tek dosya bileşim köküdür. Bu, Bileşen Uyum İlkeleri
dersindeki ayrımın aynısıdır: role göre gruplama bir ailenin parçalarını üç dosyaya dağıtır,
aileye göre gruplama birlikte değişenleri bir arada tutar.

Bedel de sayılabilir. Aile sürümü ekspresi eklerken bir dosya kazandı ve dosya sayısı dörtten
beşe çıktı; ayrıca `etiket` işlevinin gövdesi üç aile dosyasında üç kez ayrı ayrı yazıldı.
Rol sürümünde etiket biçimlerinin üçü aynı dosyada yan yana durur ve etiket sözleşmesi
değişirse tek dosya düzenlenir. Aile sürümünde aynı değişiklik aile sayısı kadar dosyaya
dokunur. Kalıp bir eksene kapanır, öbürüne açılır.

Bu yüzden soyut fabrika, parçalar arasında gerçek bir uyum kısıtı yoksa uygulanmaz. Kısıt
yoksa sessiz yanlış sayısı zaten sıfırdır ve kalıp yalnız dosya sayısını artırır.

#### Özet

- Fabrika yöntemi tek bir nesnenin yaratımını bir yönteme çeker; üst tipin gövdesinde tür adı
  geçmez ve yöntemi karşılamayan alt tip ilk çağrıda hata verir.
- Soyut fabrika birbirine uyması gereken parçaları tek çağrıda birlikte üretir; ölçüsü
  yazılabilen çağrı sayısıdır.
- Çağrı yerinde eşleştirme yapılan sürümde sekiz çağrı yazılabiliyordu: dördü hata attı,
  ikisi doğruydu, ikisi hata atmadan yanlış ücret üretti; aile sürümünde çağrı sayısı 2'ye,
  sessiz yanlış sayısı 0'a indi.
- Yeni bir aile eklendiğinde iki sürüm de 11 satır ekledi, ama düzenlenen var olan dosya
  sayısı 4'ten 1'e indi ve o tek dosya bileşim köküdür.
- Kalıbın bedeli etiket sözleşmesi değiştiğinde ortaya çıkar: aile sürümünde bu değişiklik
  aile sayısı kadar dosyaya dokunur, rol sürümünde tek dosyaya.

#### Sonraki Adım

Aile işlevi çağrıldığında üç parçayı birlikte döndürüyor, ama parçaların kendisi hâlâ tek
ifadeyle kurulabilen sabitler. Kurulacak nesne çok alanlı olduğunda — gönderinin ağırlığı,
üç boyutu, iki adresi, beyan edilen değeri ve sözleşme numarası varsa — yaratım tek bir
çağrıya sıkıştırılır ve sıralı parametreler birbirinin yerine geçebilir hâle gelir. Sonraki
ders bu çağrıyı adımlara böler: argüman çiftlerinin yer değiştirdiği bütün çağrıları üreterek
sessizce kabul edilen yanlış kurulum sayısını sayar, kurulumu adlandırılmış adımlara taşır ve
zorunlu alan denetimini tek bir noktada toplar.

### Kurucu

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/kurucu

Çok alanlı bir nesnenin adım adım kurulması: sekiz sıralı parametreli çağrı ile adlandırılmış adımlardan oluşan kurucunun aynı bozma kümesiyle karşılaştırılması, yer değiştiren argümanların ve eksik alanların kaç durumda sessizce kabul edildiğinin sayılması, geçirilen parametre sayısının düşüşü ve kalıbın satır cinsinden bedeli.

Soyut fabrika üç parçayı birlikte döndürdü, ama parçaların her biri tek ifadeyle kurulabilen
bir sabitti. Kitaplığın merkezindeki nesne öyle değil: bir gönderinin ağırlığı, üç boyutu,
gönderim ve teslim ili, beyan edilen değeri ve varsa sözleşme numarası var. Sekiz alan tek bir
çağrıya sıkıştırıldığında çağrı yerinde hangi sayının hangi alana gittiği görünmez olur.

**Kurucu** (builder) kurulumu adlandırılmış adımlara böler ve nesneyi ancak son adımda üretir.
Bu dersin ölçüsü kurulumun ne kadar okunaklı göründüğü değil: aynı bozma kümesi iki tasarıma
uygulanır ve kaç bozmanın **sessizce kabul edildiği** sayılır. Kalıbın bedeli de aynı biçimde,
satır sayısıyla ölçülür.

#### Sekiz Sıralı Parametre

Birinci sürümde gönderi tek bir çağrıyla kurulur. Hiçbir alan denetlenmez; çağrıya ne
verilirse nesneye o girer.

```js
// dogrudan/gonderi.mjs — sekiz sirali parametre; hicbir alan denetlenmiyor
export function gonderiYap(agirlik, en, boy, yukseklik, gonderimIli, teslimIli, deger, sozlesmeNo) {
  return { agirlik, en, boy, yukseklik, gonderimIli, teslimIli, deger, sozlesmeNo };
}

const BOLGE = { "34": 100, "06": 115, "65": 140 };
const desi = (g) => Math.ceil((g.en * g.boy * g.yukseklik) / 5000);

export function ucret(g) {
  const taban = 3900 + Math.ceil(Math.max(g.agirlik, desi(g))) * 900;
  const katsayi = Math.max(BOLGE[g.gonderimIli] ?? 160, BOLGE[g.teslimIli] ?? 160);
  const sigorta = Math.round(g.deger * 0.004);
  const indirim = typeof g.sozlesmeNo === "string" ? 500 : 0;
  return Math.round((taban * katsayi) / 100) + sigorta - indirim;
}
```

Ücret sekiz alanın yedisini kullanır: ağırlık ile üç boyut taban ücrete, iki il kodu bölge
katsayısına, beyan değeri sigortaya, sözleşme numarası indirime girer. Yani bir alanın yanlış
yere gitmesi ücreti değiştirir.

#### Adlandırılmış Adımlar

İkinci sürümde her adım bir yöntemdir, kendi alanını denetler ve kurucunun kendisini döndürür.
Nesne `kur` çağrısında üretilir; zorunlu alanların eksiksizliği yalnız orada, tek bir noktada
denetlenir.

```js
// kurucu/gonderi.mjs — adlandirilmis adimlar; her adim kendi alanini denetler
const BOLGE = { "34": 100, "06": 115, "65": 140 };
const IL = /^[0-9]{2}$/;
const ZORUNLU = ["agirlik", "en", "boy", "yukseklik", "gonderimIli", "teslimIli", "deger"];

const sayi = (x, ust) => typeof x === "number" && x > 0 && x <= ust;

export class GonderiKurucu {
  #alan = {};

  agirlik(kg) {
    if (!sayi(kg, 50)) throw new RangeError("agirlik 0-50 kg araliginda bir sayi olmali");
    this.#alan.agirlik = kg;
    return this;
  }

  olculer(en, boy, yukseklik) {
    if (![en, boy, yukseklik].every((x) => sayi(x, 200)))
      throw new RangeError("olcu 0-200 cm araliginda bir sayi olmali");
    Object.assign(this.#alan, { en, boy, yukseklik });
    return this;
  }

  adres(gonderimIli, teslimIli) {
    if (![gonderimIli, teslimIli].every((i) => typeof i === "string" && IL.test(i)))
      throw new RangeError("il kodu iki basamakli bir dizgi olmali");
    Object.assign(this.#alan, { gonderimIli, teslimIli });
    return this;
  }

  deger(kurus) {
    if (!Number.isInteger(kurus) || kurus < 0)
      throw new RangeError("deger negatif olmayan tam sayi olmali");
    this.#alan.deger = kurus;
    return this;
  }

  sozlesme(no) {
    if (typeof no !== "string" || !/^S[0-9]{4}$/.test(no))
      throw new RangeError("sozlesme numarasi S ve dort basamak olmali");
    this.#alan.sozlesmeNo = no;
    return this;
  }

  kur() {
    const eksik = ZORUNLU.filter((k) => this.#alan[k] === undefined);
    if (eksik.length > 0) throw new TypeError(`eksik alan: ${eksik.join(", ")}`);
    return Object.freeze({ sozlesmeNo: null, ...this.#alan });
  }
}

const desi = (g) => Math.ceil((g.en * g.boy * g.yukseklik) / 5000);

export function ucret(g) {
  const taban = 3900 + Math.ceil(Math.max(g.agirlik, desi(g))) * 900;
  const katsayi = Math.max(BOLGE[g.gonderimIli] ?? 160, BOLGE[g.teslimIli] ?? 160);
  const sigorta = Math.round(g.deger * 0.004);
  const indirim = typeof g.sozlesmeNo === "string" ? 500 : 0;
  return Math.round((taban * katsayi) / 100) + sigorta - indirim;
}
```

Üç ayrıntı kalıbın parçasıdır. Her adım `this` döndürür, böylece adımlar zincirlenebilir.
Zorunlu alan listesi tek bir yerdedir, dolayısıyla eksiklik denetimi de tek yerdedir. Üretilen
nesne dondurulmuştur; kurulum bittikten sonra alan değiştirilemez ve sözleşme numarası
verilmemişse varsayılan `null` değeri `kur` tarafından konur.

```js
// kur.mjs — ayni gonderi iki bicimde kurulur
import { gonderiYap, ucret as dogrudanUcret } from "./dogrudan/gonderi.mjs";
import { GonderiKurucu, ucret as kurucuUcret } from "./kurucu/gonderi.mjs";

const g1 = gonderiYap(2.4, 30, 40, 50, "34", "06", 180000, "S1042");
const g2 = new GonderiKurucu()
  .agirlik(2.4)
  .olculer(30, 40, 50)
  .adres("34", "06")
  .deger(180000)
  .sozlesme("S1042")
  .kur();

console.log(`dogrudan ${dogrudanUcret(g1)} kurus`);
console.log(`kurucu   ${kurucuUcret(g2)} kurus`);
```

```sh
node kur.mjs
```

```
dogrudan 17125 kurus
kurucu   17125 kurus
```

Aynı ücret. Bir çağrı satırı yedi satıra çıktı; kazancın bu satırlara değip değmediği
sayılabilir.

#### Bozma Kümesinin Uygulanması

Aşağıdaki ölçer iki bozma kümesi üretir. Birincisi doğru değer dizisindeki her argüman çiftini
yer değiştirir: sekiz alan için 28 çift. İkincisi her alanı sırayla eksiltir: 8 çağrı. Her
bozulmuş çağrının sonucu dört sınıfa ayrılır — kurulum sırasında hata atanlar, sayı olmayan
bir sonuç verenler, doğru sonucun aynısını verenler ve hata atmadan farklı bir ücret üretenler.
Son sınıf sessizce kabul edilen yanlış kurulumlardır.

```js
// sinama.mjs — bozulmus cagrilari uretir ve sonuclarini siniflandirir
import { readFileSync } from "node:fs";
import { gonderiYap, ucret as dogrudanUcret } from "./dogrudan/gonderi.mjs";
import { GonderiKurucu, ucret as kurucuUcret } from "./kurucu/gonderi.mjs";

const DOGRU = [2.4, 30, 40, 50, "34", "06", 180000, "S1042"];

const dogrudanKur = (v) => dogrudanUcret(gonderiYap(...v));
const kurucuKur = (v) =>
  kurucuUcret(new GonderiKurucu().agirlik(v[0]).olculer(v[1], v[2], v[3])
    .adres(v[4], v[5]).deger(v[6]).sozlesme(v[7]).kur());

function sinifla(kur, degerler, beklenen) {
  let sonuc;
  try {
    sonuc = kur(degerler);
  } catch {
    return "hata";
  }
  if (!Number.isFinite(sonuc)) return "gecersiz";
  return sonuc === beklenen ? "ayni" : "sessiz";
}

function say(etiket, kur, kumeler) {
  const beklenen = kur(DOGRU);
  const sayim = { hata: 0, gecersiz: 0, ayni: 0, sessiz: 0 };
  for (const v of kumeler) sayim[sinifla(kur, v, beklenen)] += 1;
  console.log(`${etiket} cagri=${kumeler.length} hata=${sayim.hata} ` +
    `gecersiz=${sayim.gecersiz} ayni=${sayim.ayni} sessiz-yanlis=${sayim.sessiz}`);
}

const yerDegistirme = [];
for (let i = 0; i < DOGRU.length; i += 1)
  for (let j = i + 1; j < DOGRU.length; j += 1) {
    const v = [...DOGRU];
    [v[i], v[j]] = [v[j], v[i]];
    yerDegistirme.push(v);
  }

const eksikAlan = DOGRU.map((_, i) => DOGRU.map((d, k) => (k === i ? undefined : d)));

const enYuksekArite = (yol) =>
  Math.max(...[...readFileSync(yol, "utf8").matchAll(/\(([^)]*)\)\s*\{/g)]
    .map((m) => m[1].split(",").filter((s) => s.trim() !== "").length));

console.log(`dogrudan en-yuksek-parametre=${enYuksekArite("dogrudan/gonderi.mjs")}`);
console.log(`kurucu   en-yuksek-parametre=${enYuksekArite("kurucu/gonderi.mjs")}`);
say("dogrudan yer-degistirme", dogrudanKur, yerDegistirme);
say("kurucu   yer-degistirme", kurucuKur, yerDegistirme);
say("dogrudan eksik alan    ", dogrudanKur, eksikAlan);
say("kurucu   eksik alan    ", kurucuKur, eksikAlan);
```

```sh
node sinama.mjs
```

```
dogrudan en-yuksek-parametre=8
kurucu   en-yuksek-parametre=3
dogrudan yer-degistirme cagri=28 hata=0 gecersiz=5 ayni=4 sessiz-yanlis=19
kurucu   yer-degistirme cagri=28 hata=21 gecersiz=0 ayni=4 sessiz-yanlis=3
dogrudan eksik alan     cagri=8 hata=0 gecersiz=5 ayni=0 sessiz-yanlis=3
kurucu   eksik alan     cagri=8 hata=8 gecersiz=0 ayni=0 sessiz-yanlis=0
```

#### Sayıların Okunması

İlk iki satır Bağlaşım Türleri dersinin ölçüsüdür: bir çağrıda geçirilen en yüksek parametre
sayısı. Sekizden üçe düştü ve kalan üç parametre aynı türden üç ölçüdür (`olculer`), yani
birlikte anlam taşırlar.

Yer değiştirme kümesinde birinci sürüm 28 bozmanın hiçbirinde hata atmadı. Beşinde sonuç sayı
olmadı — bu tespit edilebilir bir bozulmadır. Dördünde sonuç değişmedi. Kalan 19 bozma hata
atmadan farklı bir ücret üretti; bunların her biri, kod incelemesinde yakalanmazsa müşteriye
yanlış tutar olarak gider.

İkinci sürümde aynı 28 bozmanın 21'i kurulum anında hata attı, çünkü sayı bekleyen bir adıma
dizgi ya da aralık dışı bir sayı geldi. Sessizce kabul edilen bozma sayısı 19'dan 3'e indi.
Kalan üçü şu üç çiftin yer değiştirmesidir: ağırlık ile en, ağırlık ile boy, ağırlık ile
yükseklik. Üçü de aynı türden, aynı aralıkta sayılardır ve hiçbir tip denetimi bunları
ayırmaz. Bu artık kurucunun çözebileceği bir problem değildir; kilogram ile santimetreyi ayrı
tiplere taşımak gerekir, yani Veri Erişim Katmanı kursunda tanıtılan değer nesnesi.

Dördü değişmedi diye "doğru" sayılmaz. Bu dört bozmanın üçü boyutların desi çarpımındaki
simetrisidir: en, boy ve yükseklik birbirinin yerine geçtiğinde çarpım aynı kalır ve ücret
değişmez. Dördüncüsü gönderim ve teslim ilinin yer değiştirmesidir; bu tarife iki ilin bölge
katsayısının büyüğünü aldığı için ücret aynı çıkar. Ölçü o hatayı görmez, çünkü ücret o hataya
duyarlı değildir.

Eksik alan kümesi kalıbın ikinci kazancını verir. Birinci sürüm 8 eksiltmenin hiçbirinde hata
vermedi: beşi sayı olmayan bir ücret, üçü sessizce yanlış bir ücret üretti. İkinci sürümde
sekizinin sekizi `kur` çağrısında durdu ve hata iletisi hangi alanın eksik olduğunu adıyla
söyledi. Nesnenin değişmezini tek bir noktada denetlemenin ölçülebilir karşılığı budur.

#### Bedel

Kalıbın maliyeti satır sayısında görünür.

```sh
for d in dogrudan/gonderi.mjs kurucu/gonderi.mjs; do
  echo "$d: $(grep -c '' $d) satir"
done
```

```
dogrudan/gonderi.mjs: 15 satir
kurucu/gonderi.mjs: 60 satir
```

Aynı nesneyi kuran modül 15 satırdan 60 satıra çıktı ve çağrı yeri bir satırdan yedi satıra
uzadı. Bedelin bir kısmı denetimlere ait, bir kısmı adım yöntemlerinin kendisine. Kurucu bu
yüzden alan sayısı azken uygulanmaz: iki üç alanlı bir nesnede yer değiştirme kümesi zaten
küçüktür ve 45 satırlık ek metnin karşılığı çıkmaz.

İkinci bir bedel zincirin yarım kalabilmesidir. `kur` çağrılmadığı sürece kurucu nesnesi elde
tutulabilir ve yarı kurulmuş bir durum taşır. Bu durumun dışarı sızmaması, `kur` dışında hiçbir
adımın nesne döndürmemesine bağlıdır; kalıbın doğru uygulanması bu kısıtla birlikte gelir.

#### Özet

- Kurucu çok alanlı bir nesnenin kurulumunu adlandırılmış adımlara böler ve nesneyi yalnız son
  adımda üretir; zorunlu alan denetimi o tek noktada toplanır.
- Bir çağrıda geçirilen en yüksek parametre sayısı 8'den 3'e düştü ve kalan üç parametre aynı
  türden üç ölçüdür.
- 28 argüman yer değiştirmesinden sessizce kabul edilenlerin sayısı 19'dan 3'e indi; kalan üçü
  aynı türden sayıların yer değiştirmesidir ve ancak ayrı tiplerle önlenebilir.
- 8 eksik alan çağrısı birinci sürümde hiç hata vermedi, ikinci sürümde sekizi de hangi alanın
  eksik olduğunu adıyla söyleyerek durdu.
- Bedel 15 satırdan 60 satıra çıkan modül ve bir satırdan yedi satıra uzayan çağrı yeridir;
  alan sayısı azken kalıp bu bedeli karşılamaz.

#### Sonraki Adım

Kurucu her nesneyi sıfırdan kuruyor. Oysa kitaplıkta kurulacak nesnelerin çoğu birbirinin
küçük bir sapmasıdır: sözleşmeli bir müşterinin tarifesi standart tarifenin yalnız iki alanı
değişmiş hâlidir, bir bölgeye özel tarife de öyle. Bu durumda bütün adımları yeniden yürütmek
hem çağrı satırını uzatır hem de standart tarifeye eklenen her yeni alanın türetilmiş
tarifelere elle taşınmasını gerektirir. Sonraki ders nesneyi kurmak yerine var olan bir nesneyi
kopyalayıp değiştirmeyi ele alır: standart tarifeye yeni bir alan eklendiğinde elle yazan ile
kopyalayan sürümlerde kaç türetilmiş tarifede alanın eksik kaldığını sayar ve sığ kopyanın
prototiple kaç alt nesne paylaştığını ölçer.

### Prototip

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/prototip

Nesneyi kurmak yerine var olan bir nesneyi kopyalayarak yaratma: türetilmiş tarifelerin alanlarını elle yazan sürüm ile prototipi klonlayan sürümün karşılaştırılması, prototipe yeni bir alan eklendiğinde eksik alan sayısının ölçülmesi, sığ kopyanın paylaştığı alt nesne sayısının sayılması ve türetme zincirinin sessiz değişiklik olarak geri dönen bedeli.

Kurucu her nesneyi sıfırdan kurar. Kitaplıkta kurulacak nesnelerin çoğu ise birbirinin küçük
bir sapmasıdır: sözleşmeli müşteri tarifesi standart tarifenin iki alanı değişmiş hâli, bölgesel
tarife bir bölge katsayısı değişmiş hâli, kurumsal tarife de sözleşmeli tarifenin bir alanı
değişmiş hâli. Bu nesneleri adım adım kurmak, aynı alanları defalarca yazmak demektir.

**Prototip** (prototype) kalıbı yaratımı kurulumdan kopyalamaya çevirir: yeni nesne, var olan
bir nesnenin kopyası alınıp üzerinde birkaç alan değiştirilerek elde edilir. Bu dersin iki
ölçüsü var. Birincisi kazanç: prototipe yeni bir alan eklendiğinde türetilmiş nesnelerde kaç
alan eksik kalıyor. İkincisi bedel: aynı bağın istenmeyen bir değişikliği kaç nesneye taşıdığı
ve kopyanın kaç alt nesneyi prototiple paylaştığı.

#### Elle Yazılan Türetilmiş Tarifeler

Birinci sürümde dört tarife de bağımsızdır. Her biri beş alanını kendi metninde taşır.

```js
// elle/tarife.mjs — her turetilmis tarife butun alanlarini yeniden yazar
export const standart = {
  ad: "standart",
  kademeler: [[1, 3900], [5, 6400], [20, 11800]],
  bolge: { "34": 100, "06": 115, "65": 140 },
  asgariUcret: 3900,
  sigortaOrani: 0.004,
};

export const sozlesmeli = {
  ad: "sozlesmeli",
  kademeler: [[1, 3900], [5, 6400], [20, 11800]],
  bolge: { "34": 100, "06": 115, "65": 140 },
  asgariUcret: 3200,
  sigortaOrani: 0.003,
};

export const bolgesel = {
  ad: "bolgesel",
  kademeler: [[1, 3900], [5, 6400], [20, 11800]],
  bolge: { "34": 100, "06": 115, "65": 125 },
  asgariUcret: 3900,
  sigortaOrani: 0.004,
};

export const kurumsal = {
  ad: "kurumsal",
  kademeler: [[1, 3900], [5, 6400], [20, 11800]],
  bolge: { "34": 100, "06": 115, "65": 140 },
  asgariUcret: 3200,
  sigortaOrani: 0.002,
};
```

Kademe tablosu dört kez, bölge tablosu üç kez aynı biçimde yazılmıştır. Bu, Temiz Kod kursunda
tanımlanan bilgi yinelemesidir: dört nesnenin kademe tablosunun eşit kalması hiçbir düzenekle
güvence altında değildir.

#### Prototipten Klonlama

İkinci sürümde yalnız standart tarife tam yazılır. Diğerleri `tureti` çağrısıyla elde edilir:
prototip klonlanır, ada ve türetildiği prototipin adına yazılır, sonra istenen alanlar üzerine
geçirilir.

```js
// prototip/tarife.mjs — turetilmis tarifeler prototipi klonlayip alan degistirir
export const standart = {
  ad: "standart",
  kademeler: [[1, 3900], [5, 6400], [20, 11800]],
  bolge: { "34": 100, "06": 115, "65": 140 },
  asgariUcret: 3900,
  sigortaOrani: 0.004,
};

export function tureti(prototip, ad, degisiklik) {
  const kopya = structuredClone(prototip);
  return Object.assign(kopya, { ad, tureten: prototip.ad }, degisiklik);
}

export const sozlesmeli = tureti(standart, "sozlesmeli", { asgariUcret: 3200, sigortaOrani: 0.003 });
export const bolgesel = tureti(standart, "bolgesel", { bolge: { ...standart.bolge, "65": 125 } });
export const kurumsal = tureti(sozlesmeli, "kurumsal", { sigortaOrani: 0.002 });
```

Son satır kalıbın ayırt edici yanını gösteriyor: kurumsal tarife standart tarifeden değil,
**sözleşmeli tarifeden** türer. Prototip bir sınıf değil, çalışan bir nesnedir; dolayısıyla
türetilmiş bir nesne de kendisi prototip olabilir. Bu düzeneğin JavaScript'in kendi nesne
modelindeki `[[Prototype]]` bağıyla ilgisi yoktur; JavaScript Nesne Modeli kursunda ele alınan
o bağ arama zinciri kurar, buradaki klon ise bağımsız bir nesnedir ve alanlarını kopya olarak
taşır.

Aşağıdaki betik verilen dizindeki dört tarifeyle aynı gönderinin ücretini hesaplar. İki
sürümün eşdeğerliği bununla sınanır.

```js
// fiyat.mjs — verilen dizindeki dort tarifeyle ayni gonderinin ucreti
const dizin = process.argv[2];
const t = await import(`./${dizin}/tarife.mjs`);
const G = { agirlik: 2.4, il: "65", deger: 180000 };

const ucret = (tf) => {
  const taban = tf.kademeler.find(([ust]) => G.agirlik <= ust)?.[1] ?? 15000;
  const ham = Math.round((taban * (tf.bolge[G.il] ?? 160)) / 100) +
    Math.round(G.deger * tf.sigortaOrani);
  return Math.max(ham, tf.asgariUcret);
};

for (const ad of ["standart", "sozlesmeli", "bolgesel", "kurumsal"])
  console.log(`${dizin.padEnd(9)} ${ad.padEnd(11)} ${ucret(t[ad])} kurus`);
```

```sh
node fiyat.mjs elle
node fiyat.mjs prototip
```

```
elle      standart    9680 kurus
elle      sozlesmeli  9500 kurus
elle      bolgesel    8720 kurus
elle      kurumsal    9320 kurus
prototip  standart    9680 kurus
prototip  sozlesmeli  9500 kurus
prototip  bolgesel    8720 kurus
prototip  kurumsal    9320 kurus
```

Dört ücret de aynı. Fark yalnız türetilmiş nesnelerin nasıl elde edildiğinde.

#### Sığ Kopya ile Derin Kopya

Kalıbın ilk tuzağı kopyanın derinliğidir. Nesne yayma (`{ ...prototip }`) alanları birer birer
kopyalar, ama alan bir nesneye ya da diziye başvuruyorsa kopyalanan şey başvurunun kendisidir.
Aşağıdaki ölçer prototipin iki alt nesnesi için başvuru eşitliğini sayar, sonra her iki kopyada
bir kademe değerini değiştirip prototipe ne olduğunu yazar.

```js
// paylasim.mjs — sig ve derin kopyanin prototiple paylastigi alt nesne sayisi
import { standart } from "./prototip/tarife.mjs";

const ALT = ["kademeler", "bolge"];
const paylasilan = (k) => ALT.filter((a) => k[a] === standart[a]).length;

const sig = { ...standart, ad: "sig-kopya" };
const derin = structuredClone(standart);
derin.ad = "derin-kopya";

console.log(`sig   paylasilan-alt-nesne=${paylasilan(sig)}`);
console.log(`derin paylasilan-alt-nesne=${paylasilan(derin)}`);

derin.kademeler[0][1] = 4200;
console.log(`derin kademesi degisti -> standart ilk kademe=${standart.kademeler[0][1]}`);
sig.kademeler[0][1] = 4200;
console.log(`sig   kademesi degisti -> standart ilk kademe=${standart.kademeler[0][1]}`);
```

```sh
node paylasim.mjs
```

```
sig   paylasilan-alt-nesne=2
derin paylasilan-alt-nesne=0
derin kademesi degisti -> standart ilk kademe=3900
sig   kademesi degisti -> standart ilk kademe=4200
```

Sığ kopya iki alt nesneyi prototiple paylaşıyor. Sonucu son satırda: sığ kopyanın kademesine
yazmak prototipin kademesini de değiştirdi. Bu, Bağlaşım Türleri dersinde ölçülen ortak
bağlaşımın aynısıdır — iki nesnenin davranışı ortak değiştirilebilir bir duruma bağlıdır — ve
kalıbın yanlış uygulandığı en yaygın biçimdir. Derin kopyada paylaşılan alt nesne sayısı sıfır
olduğu için prototip değişmedi.

#### Prototipe Eklenen Alanın İzlenmesi

Kalıbın kazancı prototip değiştiğinde ortaya çıkar. Aşağıdaki ölçer türetilmiş üç tarifede
tanımsız kalan alanları sayar, sigorta oranlarını listeler ve türetme zincirinin derinliğini
verir.

```js
// alan-say.mjs — turetilmis tarifelerde eksik alan, sigorta orani ve prototip zinciri
const dizin = process.argv[2];
const t = await import(`./${dizin}/tarife.mjs`);
const ALAN = ["ad", "kademeler", "bolge", "asgariUcret", "sigortaOrani", "yakitEki"];
const TURETILMIS = ["sozlesmeli", "bolgesel", "kurumsal"];

const eksik = TURETILMIS.reduce(
  (toplam, ad) => toplam + ALAN.filter((a) => t[ad][a] === undefined).length, 0);
const oran = TURETILMIS.map((ad) => t[ad].sigortaOrani).join(",");
const zincir = (ad) => (t[ad].tureten === undefined ? 0 : 1 + zincir(t[ad].tureten));
const derinlik = Math.max(...TURETILMIS.map(zincir));

console.log(`${dizin.padEnd(13)} eksik-alan=${eksik} sigorta-orani=${oran} ` +
  `zincir-derinligi=${derinlik}`);
```

Değişiklik tek bir istektir: standart tarifeye yakıt eki alanı eklenir ve sigorta oranı
yükseltilir. Aşağıdaki betik bu düzenlemeyi iki ağacın kopyalarına birebir aynı biçimde
uygular; yalnız standart tarifenin alanına dokunduğundan emin olmak için metni ad üzerinden
konumlar.

```js
// yama.mjs — standart tarifenin sigorta oranini yukseltir ve yakit eki alani ekler
import { readFileSync, writeFileSync } from "node:fs";

const yol = process.argv[2];
const metin = readFileSync(yol, "utf8");
const bas = metin.indexOf('ad: "standart"');
const alan = metin.indexOf("sigortaOrani:", bas);
const satirSonu = metin.indexOf("\n", alan);
writeFileSync(yol,
  `${metin.slice(0, alan)}sigortaOrani: 0.006,\n  yakitEki: 250,${metin.slice(satirSonu)}`);
```

```sh
node alan-say.mjs elle
node alan-say.mjs prototip
cp -r elle elle-yeni
cp -r prototip prototip-yeni
node yama.mjs elle-yeni/tarife.mjs
node yama.mjs prototip-yeni/tarife.mjs
node alan-say.mjs elle-yeni
node alan-say.mjs prototip-yeni
node fiyat.mjs elle-yeni
node fiyat.mjs prototip-yeni
```

```
elle          eksik-alan=3 sigorta-orani=0.003,0.004,0.002 zincir-derinligi=0
prototip      eksik-alan=3 sigorta-orani=0.003,0.004,0.002 zincir-derinligi=2
elle-yeni     eksik-alan=3 sigorta-orani=0.003,0.004,0.002 zincir-derinligi=0
prototip-yeni eksik-alan=0 sigorta-orani=0.003,0.006,0.002 zincir-derinligi=2
elle-yeni standart    10040 kurus
elle-yeni sozlesmeli  9500 kurus
elle-yeni bolgesel    8720 kurus
elle-yeni kurumsal    9320 kurus
prototip-yeni standart    10040 kurus
prototip-yeni sozlesmeli  9500 kurus
prototip-yeni bolgesel    9080 kurus
prototip-yeni kurumsal    9320 kurus
```

Kazanç ilk sütunda. Değişiklikten önce iki sürümde de üç alan eksikti; yakıt eki hiçbir
tarifede yoktu. Düzenlemeden sonra elle yazılan sürümde eksik alan sayısı 3'te kaldı: yeni alan
yalnız standart tarifeye girdi, türetilmiş üç tarife onu hiç görmedi. Klonlayan sürümde sayı
0'a indi, çünkü türetilmiş tarifeler yükleme anında prototipten üretilir.

Bedel ikinci ve üçüncü sütunda. Sigorta oranı sözleşmeli ve kurumsal tarifelerde üzerine
yazıldığı için değişmedi; bölgesel tarifede yazılmamıştı ve 0.004'ten 0.006'ya çıktı. Ücret
satırlarında karşılığı görünüyor: bölgesel tarifenin ücreti elle yazılan sürümde 8720 kuruşta
kaldı, klonlayan sürümde 9080 kuruşa çıktı. Bu değişiklik hiçbir yerde istenmemiştir; yalnız
prototipe dokunulmuştur.

Üçüncü sütun bu bedelin neden zor fark edildiğini söylüyor. Türetme zinciri derinliği 2'dir:
kurumsal tarife sözleşmeli tarifeden, o da standart tarifeden türer. Bir alanın nihai değerini
bulmak için zinciri geriye doğru izlemek ve her basamakta alanın üzerine yazılıp yazılmadığına
bakmak gerekir. Elle yazılan sürümde bu derinlik 0'dır: her tarifenin bütün alanları kendi
metnindedir.

İki sayının aynı düzeneğin iki yüzü olduğuna dikkat edilmelidir. Yeni alanın kendiliğinden
taşınması ile istenmeyen değişikliğin kendiliğinden taşınması aynı bağdır. Kalıp bu yüzden
türetilmiş nesnelerin gerçekten prototiple birlikte değişmesi gerektiği durumlarda uygulanır;
bağımsız değişmesi gereken nesneler için taşınan değişiklik kazanç değil kusurdur.

#### Özet

- Prototip kalıbı yeni nesneyi kurulum adımlarıyla değil, var olan bir nesnenin kopyası
  üzerinden üretir; prototip bir sınıf değil çalışan bir nesnedir, dolayısıyla türetilmiş bir
  nesne de prototip olabilir.
- Prototipe yeni bir alan eklendiğinde elle yazılan sürümde eksik alan sayısı 3'te kaldı,
  klonlayan sürümde 0'a indi.
- Aynı düzenleme bölgesel tarifenin sigorta oranını 0.004'ten 0.006'ya taşıdı ve ücretini 8720
  kuruştan 9080 kuruşa çıkardı; bu değişiklik istenmemişti.
- Sığ kopya prototiple 2 alt nesne paylaştı ve kopyanın kademesine yazmak prototipin kademesini
  de değiştirdi; derin kopyada paylaşılan alt nesne sayısı 0'dır.
- Türetme zinciri derinliği klonlayan sürümde 2, elle yazılan sürümde 0'dır; bir alanın nihai
  değeri ancak zincir izlenerek bulunur.

#### Sonraki Adım

Dört yaratımsal düzenek de nesnenin nereden geldiğini düzenledi, ama hepsinde nesne çağıran
tarafından elde tutuldu. Bir sonraki soru nesnenin **kaç tane** olduğudur. Kitaplıkta tarife
kaydı gibi tek bir örneğinin bulunması istenen nesneler var; bu isteği karşılamanın en kısa
yolu nesneyi modül düzeyinde kurup her yerden aynı örneğe erişmektir. Sonraki ders bunun
sınanabilirlik cinsinden bedelini ölçer: aynı sınama kümesi tekil örnek üzerinden
koşturulduğunda kaç sınamanın başarısız olduğunu, sıra değiştiğinde sonucun değişip
değişmediğini ve durumu sıfırlayan düzeneğin dışa açılan yüzeye kaç ad eklediğini sayar.

### Tekil

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/tekil

Tek örneğin küresel durum olarak tutulmasının sınanabilirlik cinsinden ölçülmesi: aynı dört sınamanın modül düzeyinde tek örnek tutan kayıt ile örnek üreten kayda uygulanması, başarısız sınama sayısının sayılması, sınama sırası değiştirildiğinde sonucun değişmesi ve sıfırlama düzeneğinin dışa açılan yüzeye eklediği adın ölçülmesi.

Dört yaratımsal düzenek nesnenin nereden geldiğini düzenledi; hiçbiri nesnenin kaç tane
olduğunu sormadı. Kitaplıkta tek örneğinin bulunması istenen nesneler var: tarife kaydı bunların
başında geliyor, çünkü iki ayrı kayıt aynı ada iki farklı tarife bağlayabilir ve aynı gönderi
iki farklı ücret alabilir.

**Tekil** (singleton) bu isteği nesnenin kendisine yazar: tip kendi örneğini kurar, tek
tuttuğunu güvenceye alır ve o örneğe küresel erişim verir. Klasik biçiminde kurucu dışarıya
kapatılır ve erişim tek bir sınıf yöntemiyle verilir. Modül sistemi olan bir dilde bu iş zaten
modülün kendisiyle yapılır: bir modülün gövdesi süreç başına bir kez çalışır ve modül düzeyindeki
durum bütün ithal edenler için aynıdır. Bu ders kalıbın bedelini tek bir ölçüyle sayar:
sınanabilirlik.

#### Modül Düzeyinde Tek Örnek

Birinci sürümde kayıt bir modüldür. Durum modül düzeyinde durur, ilk ithalde kurulur ve
süreç boyunca tek kalır.

```js
// tekil/tarifeler/standart.mjs — standart tarife tanimi
export const STANDART = { ad: "standart", taban: () => 3900 };
```

```js
// tekil/kayit.mjs — modul duzeyinde tek ornek; durum ilk ithalde kurulur
import { STANDART } from "./tarifeler/standart.mjs";

const TARIFELER = new Map();

export function kaydet(tarifeNesnesi) {
  TARIFELER.set(tarifeNesnesi.ad, tarifeNesnesi);
}

export function tarife(ad) {
  const t = TARIFELER.get(ad);
  if (t === undefined) throw new RangeError(`bilinmeyen tarife: ${ad}`);
  return t;
}

export const adlar = () => [...TARIFELER.keys()];

export function sifirla() {
  TARIFELER.clear();
  kaydet(STANDART);
}

sifirla();
```

Son satır kalıbın belirleyici yanıdır: durum modül yüklenirken kurulur, dolayısıyla ithal eden
hiçbir modül kurulum anını seçemez. `TARIFELER` dışa verilmemiştir, ama `kaydet` çağıran her
modül onu değiştirir.

#### Aynı Kaydın Örnek Olarak Kurulması

İkinci sürümde kayıt bir tiptir ve durum örneğin özel alanında durur. Kaç örnek kurulacağına
modül karar vermez; bunu çağıran belirler.

```js
// ornek/tarifeler/standart.mjs — standart tarife tanimi
export const STANDART = { ad: "standart", taban: () => 3900 };
```

```js
// ornek/kayit.mjs — her cagri bagimsiz bir kayit ornegi uretir
import { STANDART } from "./tarifeler/standart.mjs";

export class TarifeKaydi {
  #tarifeler = new Map();

  kaydet(tarifeNesnesi) {
    this.#tarifeler.set(tarifeNesnesi.ad, tarifeNesnesi);
    return this;
  }

  tarife(ad) {
    const t = this.#tarifeler.get(ad);
    if (t === undefined) throw new RangeError(`bilinmeyen tarife: ${ad}`);
    return t;
  }

  adlar() {
    return [...this.#tarifeler.keys()];
  }
}

export const yeniKayit = () => new TarifeKaydi().kaydet(STANDART);
```

Uygulamanın tek kayıtla çalışması gereksinimi ortadan kalkmıyor; yer değiştiriyor. Bileşim kökü
`yeniKayit` çağrısını bir kez yapar ve elde ettiği örneği kullanacak modüllere verir. Yani
"tek örnek" kararı nesnenin kendisinden bileşim köküne taşınmıştır.

#### Aynı Sınamaların İki Sürüme Uygulanması

İki sürümün dışa verdiği yöntem adları aynıdır: `kaydet`, `tarife`, `adlar`. Bu yüzden aynı
sınama kümesi ikisine de uygulanabilir. Sınamalar kayıt sağlayıcısını parametre olarak alır;
tekil sürümde sağlayıcı her çağrıda aynı modülü, örnek sürümünde her çağrıda yeni bir örnek
döndürür.

```js
// sinamalar.mjs — dort sinama; her biri bir kayit saglayicisi alir
import assert from "node:assert/strict";

export const SINAMALAR = [
  ["yuklenen tarife sayisi", (kayitAl) => assert.equal(kayitAl().adlar().length, 1)],
  ["yeni tarife kaydedilir", (kayitAl) => {
    const k = kayitAl();
    k.kaydet({ ad: "indirimli", taban: () => 3200 });
    assert.equal(k.tarife("indirimli").taban(), 3200);
  }],
  ["kayit yalniz yuklenenleri icerir", (kayitAl) => assert.equal(kayitAl().adlar().length, 1)],
  ["bilinmeyen tarife hata verir",
    (kayitAl) => assert.throws(() => kayitAl().tarife("indirimli"), RangeError)],
];
```

```js
// duz.test.mjs — dort sinama tanimlandigi sirada iki surume uygulanir
import test from "node:test";
import { SINAMALAR } from "./sinamalar.mjs";
import * as tekilKayit from "./tekil/kayit.mjs";
import { yeniKayit } from "./ornek/kayit.mjs";

const SURUM = [["tekil", () => tekilKayit], ["ornek", () => yeniKayit()]];

for (const [surum, kayitAl] of SURUM)
  for (const [ad, sinama] of SINAMALAR) test(`${surum}: ${ad}`, () => sinama(kayitAl));
```

```sh
node --test --test-reporter=tap duz.test.mjs 2>&1 | grep -E '^(not ok|# (pass|fail))'
```

```
not ok 3 - tekil: kayit yalniz yuklenenleri icerir
not ok 4 - tekil: bilinmeyen tarife hata verir
# pass 6
# fail 2
```

Sekiz sınamanın ikisi başarısız ve ikisi de tekil sürüme ait. Örnek sürümünde dördün dördü
geçti. Başarısız iki sınamanın kendi içinde bir kusuru yok: üçüncü sınama kayıtta yalnız
yüklenen tarifelerin bulunmasını, dördüncüsü bilinmeyen bir tarifenin hata vermesini bekliyor.
İkisini de bozan şey ikinci sınamanın çalışmış olmasıdır — ikinci sınama kayda `indirimli`
tarifesini yazdı ve o yazma süreç boyunca kaldı.

Bu, Bağlaşım Türleri dersindeki ortak bağlaşımın tanımının aynısıdır: bir birimin davranışı,
onu hiç ithal etmemiş bir birim tarafından değiştirilebiliyor. Üçüncü sınama ikinci sınamayı
ithal etmez, adını bilmez, sırasını seçmez; buna karşın sonucu ona bağlıdır.

#### Sıranın Değiştirilmesi

Bağın gerçekten sıraya bağlı olduğu, aynı dört sınama ters sırada koşturularak sınanır.

```js
// ters.test.mjs — ayni dort sinama ters sirada kosar
import test from "node:test";
import { SINAMALAR } from "./sinamalar.mjs";
import * as tekilKayit from "./tekil/kayit.mjs";
import { yeniKayit } from "./ornek/kayit.mjs";

const SURUM = [["tekil", () => tekilKayit], ["ornek", () => yeniKayit()]];

for (const [surum, kayitAl] of SURUM)
  for (const [ad, sinama] of [...SINAMALAR].reverse())
    test(`${surum}: ${ad}`, () => sinama(kayitAl));
```

```sh
node --test --test-reporter=tap ters.test.mjs 2>&1 | grep -E '^(not ok|# (pass|fail))'
```

```
not ok 4 - tekil: yuklenen tarife sayisi
# pass 7
# fail 1
```

Başarısız sınama sayısı 2'den 1'e indi ve başarısız olan sınama değişti: düz sırada üçüncü ve
dördüncü sınamalar, ters sırada birinci sınama çöktü. Sınamaların metnine, kayıt modüllerine ve
tarife tanımına dokunulmadı; değişen tek şey sıra oldu. Frontend Kalitesi kursunda tanımlanan
kırılgan test tam olarak budur ve sayısı tekil sürümde 2, örnek sürümünde 0'dır.

Örnek sürümü iki koşumda da dört sınamanın dördünü geçti, çünkü her sınama kendi kaydını kurar
ve bitince bırakır.

#### Sıfırlamanın Bedeli

Tekil sürüm sınanabilir hâle getirilebilir; bunun için durumu sıfırlayan bir yöntem ve her
sınamadan önce onu çağıran bir adım gerekir.

```js
// sifirlamali.test.mjs — tekil surum her sinamadan once sifirlanir
import test, { beforeEach } from "node:test";
import { SINAMALAR } from "./sinamalar.mjs";
import * as tekilKayit from "./tekil/kayit.mjs";

beforeEach(() => tekilKayit.sifirla());

for (const [ad, sinama] of SINAMALAR) test(`tekil: ${ad}`, () => sinama(() => tekilKayit));
```

```sh
node --test --test-reporter=tap sifirlamali.test.mjs 2>&1 | grep -E '^(not ok|# (pass|fail))'
for d in tekil/kayit.mjs ornek/kayit.mjs; do
  echo "$d: $(grep -c 'export ' $d) disa acilan ad"
done
```

```
# pass 4
# fail 0
tekil/kayit.mjs: 4 disa acilan ad
ornek/kayit.mjs: 2 disa acilan ad
```

Dördün dördü geçti; çözüm işliyor. Bedeli iki sayıda. Birincisi dışa açılan ad sayısıdır: tekil
sürüm dört ad veriyor, örnek sürümü iki. Fazladan çıkan `sifirla` adı üretimde hiçbir yerde
çağrılmaz; yalnız sınamalar için vardır. Temiz Kod kursundaki en dar görünürlük ilkesine göre bu
ad ölü yüzeydir ve üretim kodundan da çağrılabilir olması yeni bir hata biçimi açar.

İkinci bedel sayının kendisinde değil, sürdürülmesinde. Sıfırlama listesi elle tutulur: kayda
ikinci bir küresel alan eklendiğinde `sifirla` de düzenlenmelidir, yoksa sınamalar yeniden
sıraya bağlı hâle gelir ve bu bağ ancak sıra değiştiğinde görünür. Örnek sürümünde böyle bir
liste yoktur, çünkü sıfırlanacak durum yeni örnekle birlikte zaten yoktur.

#### Tekil Ne Zaman Uygulanır

Ölçüler kalıbı tümden yasaklamıyor, uygulanabildiği bağlamı daraltıyor. Tekil, tuttuğu durum
süreç boyunca **değişmiyorsa** ölçüleri bozmaz: sabit bir tablo, bir kez okunan bir yapılandırma
ya da değiştirilemez bir eşleme modül düzeyinde durabilir, çünkü sınamalar arasında taşınacak
bir değişiklik yoktur. Ölçüleri bozan şey tekillik değil, tekil örneğin **değişebilir**
olmasıdır.

Kayıt bu ölçüte uymuyor: `kaydet` yöntemi durumu değiştirir. Kitaplıkta tekil olarak
bırakılabilecek şey tarife nesnelerinin kendisidir — dondurulmuş, davranışı sabit nesneler —
kayıt değil.

#### Özet

- Tekil, tek örneği nesnenin kendi sorumluluğuna yazar ve ona küresel erişim verir; modül
  sistemi olan bir dilde modül düzeyindeki durum bu kalıbı zaten gerçekleştirir.
- Aynı dört sınama iki sürüme uygulandığında tekil sürümde 2 sınama başarısız oldu, örnek
  sürümünde 4 sınamanın dördü geçti.
- Başarısızlığın nedeni sınamaların kendisi değil sırasıdır: sınamalar ters sırada koşturulunca
  başarısız sayısı 2'den 1'e indi ve başarısız olan sınama değişti.
- Tekil sürüm sıfırlama yöntemiyle sınanabilir hâle geldi ve dört sınamanın dördü geçti; bedel
  dışa açılan ad sayısının 2'den 4'e çıkması ve elle sürdürülen bir sıfırlama listesidir.
- Tekil, tuttuğu durum değiştirilemez olduğunda ölçüleri bozmaz; bozan şey tekillik değil, tek
  örneğin değişebilir olmasıdır.

#### Sonraki Adım

Örnek sürümü sınamaları geçirdi, ama bir soruyu açıkta bıraktı: kaydı kullanan modül o örneği
nereden alacak. Modülün içinde `yeniKayit` çağrılırsa her modül kendi kaydını kurar ve tek örnek
gereksinimi çiğnenir; modül tekil sürümü ithal ederse ölçüler geri gelir. Sonraki ders bu
soruyu yaratım sorumluluğunun taşınması olarak ele alır: bir modülün kendi bağımlılığını
kurduğu sürümle bağımlılığı dışarıdan aldığı sürümde ücret modülünün doğrudan bağ sayısını ve
ithal kapanışının boyutunu karşılaştırır.

### Bağımlılık Enjeksiyonu

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/bagimlilik-enjeksiyonu

Yaratım sorumluluğunun kullanan koddan alınıp bileşim köküne taşınması: bağımlılıklarını kendisi kuran ücret modülü ile bağımlılıklarını çağrıda alan sürümün doğrudan bağ sayısı ve ithal kapanışı boyutuyla karşılaştırılması, indirim politikası değiştiğinde düzenlenen dosyanın sayılması ve kapanışın yok olmayıp yer değiştirdiğinin gösterilmesi.

Bir önceki ders kayıt için örnek üreten sürümü sınamalardan geçirdi, ama kaydı kullanan modülün
o örneği nereden alacağını söylemedi. Modül `yeniKayit` çağrısını kendi gövdesinde yaparsa her
modül kendi kaydını kurar; modül tekil sürümü ithal ederse küresel durum geri gelir. Üçüncü
seçenek nesneyi hiç kurmamaktır.

**Bağımlılık enjeksiyonu** Frontend Mimarisi ve Birim Testi kurslarında tanımlanmış bir
düzenektir; burada yeniden tanımlanmaz. Bu dersin sorduğu şey daha dar: bir modülün
bağımlılığını kendisi kurması ile dışarıdan alması arasındaki fark hangi sayılarla görünür. İki
ölçü kullanılır: bir modülün **doğrudan bağ sayısı** ve **ithal kapanışı boyutu**; ikincisi
Veri Erişim Katmanı ve İş Mantığı kursundan devralınır.

#### Sağlayıcı Modüller

İki sürüm de aynı üç parçayı kullanır: tarife nesneleri, onları tutan bir kayıt ve bir indirim
politikası. Bu parçalar iki sürüm için ortaktır.

```js
// saglayici/tarife.mjs — iki tarife nesnesi
export const STANDART = { ad: "standart", taban: (g) => 3900 + Math.ceil(g.agirlik) * 900 };
export const EKSPRES = { ad: "ekspres", taban: (g) => 6400 + Math.ceil(g.agirlik) * 1500 };
```

```js
// saglayici/kayit.mjs — tarife kaydi; iki tarife yuklenmis bir ornek uretir
import { STANDART, EKSPRES } from "./tarife.mjs";

export class TarifeKaydi {
  #tarifeler = new Map();

  kaydet(tarifeNesnesi) {
    this.#tarifeler.set(tarifeNesnesi.ad, tarifeNesnesi);
    return this;
  }

  tarife(ad) {
    const t = this.#tarifeler.get(ad);
    if (t === undefined) throw new RangeError(`bilinmeyen tarife: ${ad}`);
    return t;
  }
}

export const yeniKayit = () => new TarifeKaydi().kaydet(STANDART).kaydet(EKSPRES);
```

```js
// saglayici/indirim.mjs — iki indirim politikasi
export const sozlesmeliIndirim = (gonderi, ham) =>
  gonderi.sozlesmeNo === null ? 0 : Math.round(ham * 0.12);

export const indirimsiz = () => 0;
```

#### Bağımlılığını Kendisi Kuran Modül

Birinci sürümde ücret modülü ihtiyacı olan her şeyi kendisi ithal eder ve kaydı kendisi kurar.
Çağıran taraf yalnız `ucret` işlevini görür.

```js
// kendi/ucret.mjs — bagimliliklarini kendisi ithal eder ve kendisi kurar
import { yeniKayit } from "../saglayici/kayit.mjs";
import { sozlesmeliIndirim } from "../saglayici/indirim.mjs";

const BOLGE = { "34": 100, "06": 115, "65": 140 };
const KAYIT = yeniKayit();

export function ucret(gonderi) {
  const taban = KAYIT.tarife(gonderi.tarife).taban(gonderi);
  const ham = Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 160)) / 100);
  return ham - sozlesmeliIndirim(gonderi, ham);
}
```

```js
// kendi/kur.mjs — cagiran yalniz ucret islevini ithal eder
import { ucret } from "./ucret.mjs";

export const ORNEK = [
  { tarife: "standart", agirlik: 2.4, adres: "34100", sozlesmeNo: "S1042" },
  { tarife: "ekspres", agirlik: 2.4, adres: "06500", sozlesmeNo: null },
];

for (const g of ORNEK) console.log(`kendi   ${g.tarife.padEnd(9)} ${ucret(g)} kurus`);
```

```sh
node kendi/kur.mjs
```

```
kendi   standart  5808 kurus
kendi   ekspres   12535 kurus
```

Bu sürümün çağrı yeri en kısa olanıdır: tek bir ithal ve tek bir çağrı. Bedeli çağrı yerinde
değil, ücret modülünün kime bağlandığında.

#### Bağımlılığını Çağrıda Alan Modül

İkinci sürümde ücret modülü hiçbir şey ithal etmez. Kayıt ve indirim politikası çağrıda verilir;
modül bir hesap işlevi üretir.

```js
// enjekte/ucret.mjs — bagimliliklari cagrida alir; hicbir modul ithal etmez
const BOLGE = { "34": 100, "06": 115, "65": 140 };

export const ucretHesabiYap = ({ kayit, indirim }) => (gonderi) => {
  const taban = kayit.tarife(gonderi.tarife).taban(gonderi);
  const ham = Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 160)) / 100);
  return ham - indirim(gonderi, ham);
};
```

Kurulum işi bileşim köküne geçer. Kök parçaları kurar, hangi indirim politikasının
kullanılacağını seçer ve hesabı üretir.

```js
// enjekte/kur.mjs — bilesim koku: parcalari kurar, secer ve enjekte eder
import { yeniKayit } from "../saglayici/kayit.mjs";
import { sozlesmeliIndirim, indirimsiz } from "../saglayici/indirim.mjs";
import { ucretHesabiYap } from "./ucret.mjs";

const POLITIKA = { sozlesmeli: sozlesmeliIndirim, yok: indirimsiz };
const secim = process.argv[2] ?? "sozlesmeli";
const ucret = ucretHesabiYap({ kayit: yeniKayit(), indirim: POLITIKA[secim] });

export const ORNEK = [
  { tarife: "standart", agirlik: 2.4, adres: "34100", sozlesmeNo: "S1042" },
  { tarife: "ekspres", agirlik: 2.4, adres: "06500", sozlesmeNo: null },
];

for (const g of ORNEK)
  console.log(`enjekte ${secim.padEnd(11)} ${g.tarife.padEnd(9)} ${ucret(g)} kurus`);
```

```sh
node enjekte/kur.mjs
node enjekte/kur.mjs yok
```

```
enjekte sozlesmeli  standart  5808 kurus
enjekte sozlesmeli  ekspres   12535 kurus
enjekte yok         standart  6600 kurus
enjekte yok         ekspres   12535 kurus
```

İlk iki satır birinci sürümün çıktısıyla birebir aynı. Son iki satır indirim politikası
değiştirildiğinde ne olduğunu gösteriyor: sözleşmeli gönderinin ücreti 5808 kuruştan 6600 kuruşa
çıktı, sözleşmesiz gönderinin ücreti değişmedi. İki koşum arasında hiçbir dosya düzenlenmedi.

#### Bağların ve Kapanışın Sayılması

Aşağıdaki ölçer verilen modüllerin doğrudan ithal ettiği modül sayısını ve ithal kapanışının
boyutunu verir. Kapanış, modülün ulaştığı bütün modüllerin geçişli kümesidir.

```js
// kapanis.mjs — verilen modullerin dogrudan bag sayisi ve ithal kapanisi boyutu
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";

const ITHAL = /(?:from|import)\s*["'](\.[^"']+)["']/g;
const baglar = (yol) => [...readFileSync(yol, "utf8").matchAll(ITHAL)].map((m) => m[1]);

function kapanis(yol, gorulen = new Set()) {
  for (const bag of baglar(yol)) {
    const hedef = resolve(dirname(yol), bag);
    if (!gorulen.has(hedef)) {
      gorulen.add(hedef);
      kapanis(hedef, gorulen);
    }
  }
  return gorulen;
}

for (const yol of process.argv.slice(2))
  console.log(`${yol.padEnd(18)} dogrudan-bag=${baglar(yol).length} ` +
    `ithal-kapanisi=${kapanis(yol).size}`);
```

```sh
node kapanis.mjs kendi/ucret.mjs enjekte/ucret.mjs kendi/kur.mjs enjekte/kur.mjs
```

```
kendi/ucret.mjs    dogrudan-bag=2 ithal-kapanisi=3
enjekte/ucret.mjs  dogrudan-bag=0 ithal-kapanisi=0
kendi/kur.mjs      dogrudan-bag=1 ithal-kapanisi=4
enjekte/kur.mjs    dogrudan-bag=3 ithal-kapanisi=4
```

İlk iki satır kazancı veriyor. Ücret modülünün doğrudan bağ sayısı 2'den 0'a, ithal kapanışı
boyutu 3'ten 0'a indi. Sıfır kapanış, modülün metninde hiçbir somut ayrıntının adının geçmediği
anlamına gelir: kayıt sınıfının adı, tarife nesnelerinin adı ve indirim işlevinin adı ücret
modülünde yoktur.

Son iki satır kalıbın gerçekte ne yaptığını söylüyor. İki sürümün toplam kapanışı da 4'tür.
Yani kapanış yok olmadı, **yer değiştirdi**: birinci sürümde ücret modülünün üstündeydi, ikinci
sürümde bileşim kökünün. Bileşim kökünün doğrudan bağ sayısı 1'den 3'e çıktı. Yaratım
sorumluluğunun taşınması ölçüde tam olarak böyle görünür — bir modülün kazandığı bağımsızlık
başka bir modülün kazandığı bağ sayısıdır.

#### Politika Değiştiğinde Düzenlenen Dosya

İkinci ölçü şu istekle çıkar: indirim politikası kaldırılacak. Aşağıdaki betik iki ağacı
kopyalar, isteği ikisine de uygular, sonuçları koşturur ve düzenlenen dosyaları sayar. Yerinde
düzenlemede yedek uzantısı verilmiştir; GNU ve BSD `sed` bu biçimde aynı davranır.

```sh
cp -r kendi kendi-yok
cp -r enjekte enjekte-yok
sed -i.y 's/sozlesmeliIndirim/indirimsiz/g' kendi-yok/ucret.mjs
rm -f kendi-yok/*.y
node kendi-yok/kur.mjs
node enjekte-yok/kur.mjs yok

for p in kendi enjekte; do
  echo "$p duzenlenen dosya = $(diff -rq $p $p-yok | grep -c '^Files ')"
  echo "$p ucret modulu degisti mi = $(diff -q $p/ucret.mjs $p-yok/ucret.mjs > /dev/null && echo hayir || echo evet)"
done
```

```
kendi   standart  6600 kurus
kendi   ekspres   12535 kurus
enjekte yok         standart  6600 kurus
enjekte yok         ekspres   12535 kurus
kendi duzenlenen dosya = 1
kendi ucret modulu degisti mi = evet
enjekte duzenlenen dosya = 0
enjekte ucret modulu degisti mi = hayir
```

İlk dört satır iki ağacın aynı ücretleri ürettiğini gösteriyor; ilk iki satırdaki `kendi`
etiketi modül metninde sabit olduğu için kopyada da öyle kalmıştır. Son dört satır ölçüyü
veriyor: birinci sürümde istek karşılanırken bir dosya düzenlendi ve o dosya ücret modülünün
kendisiydi. İkinci sürümde hiçbir dosya düzenlenmedi; politika seçimi zaten bileşim kökünün
dışında, çalışma anında verilen bir değerdi.

Bu, Açık–Kapalı İlkesi dersindeki ayrımın aynısıdır ve son satır belirleyici olandır: ücret
hesabını yapan modül birinci sürümde değişti, ikincisinde değişmedi.

#### Bedel

Kalıbın bedeli üç yerde. Birincisi çağrı yerinin uzunluğu: ücret hesabını elde etmek artık tek
bir ithal değil, üç ithal ve bir kurulum çağrısıdır. İkincisi bileşim kökünün doğrudan bağ
sayısının 1'den 3'e çıkmasıdır; bileşim kökü kaçınılmaz olarak her somut parçayı bilen tek
modüldür ve büyümesi bileşen sayısıyla doğru orantılıdır.

Üçüncüsü okunurluk cinsinden değil, izlenmesi gereken çağrı sayısı cinsindendir. Birinci sürümde
`ucret` işlevinin hangi indirim politikasını kullandığı aynı dosyada, ithal satırında yazılıdır.
İkinci sürümde bu bilgi ücret modülünde yoktur; onu bulmak için bileşim kökü okunmalı ve orada
`POLITIKA` tablosunun hangi girdisinin seçildiği izlenmelidir. Yani bir sorunun cevabı bir
dosyadan iki dosyaya yayılmıştır.

Bu yüzden bağımlılık enjeksiyonu, bağımlılığın gerçekten değişebilir olduğu yerlerde uygulanır.
Bölge tablosu iki sürümde de ücret modülünün içinde bırakılmıştır: bir sabit tablodur, süreç
boyunca değişmez ve dışarıdan verilmesi kapanışı düşürmez, yalnız bileşim kökünü büyütür.

#### Özet

- Bağımlılık enjeksiyonu yaratım sorumluluğunu nesneyi kullanan koddan alır ve bileşim köküne
  taşır; kalıbın sorusu nesnenin nasıl kurulduğu değil, kimin kurduğudur.
- Ücret modülünün doğrudan bağ sayısı 2'den 0'a, ithal kapanışı boyutu 3'ten 0'a indi; modülün
  metninde hiçbir somut parçanın adı geçmez.
- Toplam kapanış iki sürümde de 4'tür: kapanış yok olmadı, bileşim köküne taşındı ve kökün
  doğrudan bağ sayısı 1'den 3'e çıktı.
- İndirim politikası kaldırıldığında birinci sürümde 1 dosya düzenlendi ve o dosya ücret
  modülüydü; ikinci sürümde 0 dosya düzenlendi.
- Bedel çağrı yerinin uzaması, bileşim kökünün bileşen sayısıyla büyümesi ve bir bağımlılığın
  hangi somut parça olduğunun ancak bileşim kökü okunarak bulunmasıdır.

#### Sonraki Adım

Bu konudaki beş düzenek bir nesnenin kim tarafından ve nasıl kurulacağı sorusunu çözdü: tür
seçiminin bir yere toplanması, birbirine uyan parçaların birlikte üretilmesi, çok adımlı
kurulumun sıraya sokulması, var olan bir nesnenin kopyalanması, tek örneğin nerede tutulacağı ve
kurma işinin bileşim köküne taşınması. Hepsinde ortak bir varsayım vardı: kurulan nesne
kitaplığın kendi nesnesiydi ve arayüzünü de kitaplık yazıyordu, dolayısıyla parçaların
birbirine uyması sağlanabilir bir şeydi. Sonraki konu nesnelerin **nasıl birleştirileceğini**
düzenler ve ilk problem tam bu varsayımın düştüğü yerdir: şeklini kitaplığın belirlemediği bir
arayüzle çalışmak. Her dış taşıyıcı sağlayıcısı kendi sözlüğünü, kendi birimini ve hata
bildirmenin kendi biçimini getirir; sonraki ders bu uyumsuzluğun tek bir yerde çözülmesini ele
alır ve kazancını yine dokunulan dosya sayısıyla ölçer.

## Yapısal Kalıplar

Nesnelerin ve arayüzlerin bileşimle düzenlenmesi: uyumsuz arayüzlerin bağdaştırıcıyla köprülenmesi, soyutlama ile gerçekleştirimin köprü kalıbıyla ayrı evrimi, ağaç yapılarının bileşikle tek biçim ele alınması, davranışın dekoratörle katmanlanması, karmaşık alt sistemin cepheyle sadeleştirilmesi, paylaşımla bellek tasarrufu ve erişimin vekil aracılığıyla denetlenmesi.

### Bağdaştırıcı

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/bagdastirici

Uyumsuz arayüzlerin köprülenmesi: iki dış taşıyıcı sağlayıcısının sözlüğüne dokunan dosyaların çevirinin istemciye dağıldığı düzende ve bağdaştırıcı arkasındaki düzende sayılması, üçüncü sağlayıcı eklendiğinde düzenlenen dosya ile eklenen satırın karşılaştırılması ve kalıbın dosya, satır ve dolaylılık cinsinden bedelinin çıkarılması.

Yaratımsal kalıplar bir nesnenin **kim tarafından ve nasıl kurulacağı** sorusunu çözdü:
fabrika yöntemi tür seçimini bir yere topladı, kurucu çok adımlı kurulumu sıraya soktu,
bağımlılık enjeksiyonu yaratma sorumluluğunu nesneyi kullanan koddan aldı. Hepsinde kurulan
şey kitaplığın kendi nesnesiydi ve arayüzünü de kitaplık yazıyordu.

Bu konu nesnelerin **nasıl birleştirileceğini** düzenler. İlk problem, şeklini kitaplığın
belirlemediği bir arayüzle çalışmaktır: gönderi ücretlendirme kitaplığı kaç taşıyıcıyla
çalışacaksa o kadar dış sağlayıcıya bağlanır, her sağlayıcı kendi sözlüğünü, kendi birimini
ve "bu gönderiyi taşımıyorum" demenin kendi biçimini getirir. **Bağdaştırıcı** (adapter) bu
uyumsuzluğu tek bir yerde çözer. Kazancı iki sayıyla ölçülür: sağlayıcı sözlüğüne dokunan
dosya sayısı ve üçüncü bir sağlayıcı eklendiğinde düzenlenen var olan dosya sayısı.

#### Problem: İki Sözlük, İki Hata Biçimi

İki dış sağlayıcı aynı işi yapar ve hiçbir noktada uyuşmaz. Birincisi ağırlığı gram, ücreti
kuruş olarak alır ve kademe dışında `null` döndürür.

```js
// saglayici/kara.mjs — dis saglayici: gram ve kurus ile calisir, kademe disinda null doner
const KADEME = [[1000, 4990], [5000, 8490], [15000, 14990], [30000, 24990]];
const KATSAYI = { 34: 100, "06": 115, 35: 120, 65: 145 };

export function bedelHesapla({ gramaj, hedefKod }) {
  const kademe = KADEME.find(([ust]) => gramaj <= ust);
  if (kademe === undefined) return null;
  const katsayi = KATSAYI[hedefKod.slice(0, 2)] ?? 165;
  return { kurusBedel: Math.round((kademe[1] * katsayi) / 100), gunAdedi: katsayi > 130 ? 4 : 2 };
}
```

İkincisi kilogram ve lira ile çalışır, posta kodu yerine bölge adı bekler ve taşımadığı
gönderide hata atar.

```js
// saglayici/hava.mjs — dis saglayici: kilogram ve lira ile calisir, kapsam disinda hata atar
const BOLGE_TABAN = { yakin: 39.9, orta: 64.5, uzak: 98 };

export function fiyatSor({ kiloAgirlik, bolgeAdi }) {
  const taban = BOLGE_TABAN[bolgeAdi];
  if (taban === undefined) throw new RangeError(`kapsam disi bolge: ${bolgeAdi}`);
  if (kiloAgirlik > 20) throw new RangeError("azami agirlik 20 kg");
  return {
    lira: Math.round((taban + kiloAgirlik * 8.4) * 100) / 100,
    aktarmaGunu: bolgeAdi === "uzak" ? 3 : 1,
  };
}
```

Kitaplığın kendi sözleşmesi tek: bir teklif `{ tasiyici, tutar, gun }` nesnesidir, tutar
kuruştur, taşınamayan gönderi için teklif `null`'dır. Bölge tablosu ve örnek gönderiler
kitaplığın kendi malıdır; iki düzen de bunları paylaşır.

```js
// bolge.mjs — kitapligin kendi bolge tablosu ve ornek gonderiler
const POSTA_BOLGE = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" };

export const bolgeBul = (postaKodu) => POSTA_BOLGE[postaKodu.slice(0, 2)] ?? "bilinmeyen";

export const ORNEK = [
  { kod: "G-1", agirlik: 0.8, postaKodu: "34100" },
  { kod: "G-2", agirlik: 12, postaKodu: "06500" },
  { kod: "G-3", agirlik: 26, postaKodu: "65100" },
  { kod: "G-4", agirlik: 3, postaKodu: "48200" },
  { kod: "G-5", agirlik: 8, postaKodu: "35400" },
];
```

#### Çevirinin İstemciye Dağıldığı Düzen

İlk düzende çeviriyi sağlayıcıyı çağıran her yer kendisi yapar. Teklifleri sıralayan modül
gram çevirisini, lira çevirisini ve iki ayrı hata biçimini kendi gövdesinde taşır.

```js
// dogrudan/secim.mjs — secim iki saglayicinin sozlugunu ve birimini kendi icinde cevirir
import { bedelHesapla } from "../saglayici/kara.mjs";
import { fiyatSor } from "../saglayici/hava.mjs";
import { bolgeBul } from "../bolge.mjs";

export function teklifler(gonderi) {
  const cikan = [];
  const k = bedelHesapla({ gramaj: Math.round(gonderi.agirlik * 1000), hedefKod: gonderi.postaKodu });
  if (k !== null) cikan.push({ tasiyici: "kara", tutar: k.kurusBedel, gun: k.gunAdedi });
  try {
    const h = fiyatSor({ kiloAgirlik: gonderi.agirlik, bolgeAdi: bolgeBul(gonderi.postaKodu) });
    cikan.push({ tasiyici: "hava", tutar: Math.round(h.lira * 100), gun: h.aktarmaGunu });
  } catch (e) {
    if (!(e instanceof RangeError)) throw e;
  }
  return cikan.sort((a, b) => a.tutar - b.tutar);
}
```

İkinci bir soru — bir gönderiyi kaç taşıyıcının aldığı — aynı çevirinin ikinci kopyasını
doğurur. Kopyalanan şey birim değil, "taşımıyorum" ifadesinin iki sağlayıcıda iki farklı
biçim aldığı bilgisidir.

```js
// dogrudan/kapsam.mjs — ayni cevirinin ikinci kopyasi: kac saglayici tasiyabiliyor
import { bedelHesapla } from "../saglayici/kara.mjs";
import { fiyatSor } from "../saglayici/hava.mjs";
import { bolgeBul } from "../bolge.mjs";

export function tasiyanSayisi(gonderi) {
  let sayi = 0;
  if (bedelHesapla({ gramaj: Math.round(gonderi.agirlik * 1000), hedefKod: gonderi.postaKodu }) !== null) sayi += 1;
  try {
    fiyatSor({ kiloAgirlik: gonderi.agirlik, bolgeAdi: bolgeBul(gonderi.postaKodu) });
    sayi += 1;
  } catch (e) {
    if (!(e instanceof RangeError)) throw e;
  }
  return sayi;
}
```

#### Çözüm: Sağlayıcı Başına Bir Bağdaştırıcı

Kalıbın çözümü, her sağlayıcının önüne kitaplığın sözleşmesini karşılayan bir nesne
koymaktır. Bağdaştırıcı üç şeyi çevirir: alan adlarını, birimleri ve hata biçimini.

```js
// bagdastirici/kara-bagdastirici.mjs — gram/kurus sozlugunu kitapligin teklif sozlesmesine cevirir
import { bedelHesapla } from "../saglayici/kara.mjs";

export const karaBagdastirici = {
  teklif(gonderi) {
    const k = bedelHesapla({ gramaj: Math.round(gonderi.agirlik * 1000), hedefKod: gonderi.postaKodu });
    return k === null ? null : { tasiyici: "kara", tutar: k.kurusBedel, gun: k.gunAdedi };
  },
};
```

```js
// bagdastirici/hava-bagdastirici.mjs — lira/bolge sozlugunu ve hatayi ayni sozlesmeye cevirir
import { fiyatSor } from "../saglayici/hava.mjs";
import { bolgeBul } from "../bolge.mjs";

export const havaBagdastirici = {
  teklif(gonderi) {
    try {
      const h = fiyatSor({ kiloAgirlik: gonderi.agirlik, bolgeAdi: bolgeBul(gonderi.postaKodu) });
      return { tasiyici: "hava", tutar: Math.round(h.lira * 100), gun: h.aktarmaGunu };
    } catch (e) {
      if (e instanceof RangeError) return null;
      throw e;
    }
  },
};
```

Sağlayıcıyı çağıran iki modül artık tek sözleşme görür ve sağlayıcı adlarını hiç bilmez.

```js
// bagdastirici/secim.mjs — saglayici sozlugunden hicbir ad gecmez
export const teklifler = (gonderi, bagdastiricilar) =>
  bagdastiricilar
    .map((b) => b.teklif(gonderi))
    .filter((t) => t !== null)
    .sort((a, b) => a.tutar - b.tutar);
```

```js
// bagdastirici/kapsam.mjs — tek sozlesme uzerinden sayar
import { teklifler } from "./secim.mjs";

export const tasiyanSayisi = (gonderi, bagdastiricilar) => teklifler(gonderi, bagdastiricilar).length;
```

```js
// bagdastirici/kur.mjs — bilesim koku: hangi bagdastiricilarin kullanildigini yalniz burasi bilir
import { karaBagdastirici } from "./kara-bagdastirici.mjs";
import { havaBagdastirici } from "./hava-bagdastirici.mjs";

export const BAGDASTIRICILAR = [karaBagdastirici, havaBagdastirici];
```

Karşılaştırmanın anlamı için iki düzenin aynı gönderilere aynı yanıtı vermesi gerekir.

```js
// kosum.mjs — iki duzen ayni teklif sirasini ve ayni tasiyan sayisini veriyor mu
import { ORNEK } from "./bolge.mjs";
import { teklifler as dogrudanTeklif } from "./dogrudan/secim.mjs";
import { tasiyanSayisi as dogrudanSayi } from "./dogrudan/kapsam.mjs";
import { teklifler as kopruluTeklif } from "./bagdastirici/secim.mjs";
import { tasiyanSayisi as kopruluSayi } from "./bagdastirici/kapsam.mjs";
import { BAGDASTIRICILAR } from "./bagdastirici/kur.mjs";

let ayrilan = 0;
for (const g of ORNEK) {
  const a = `${JSON.stringify(dogrudanTeklif(g))} tasiyan=${dogrudanSayi(g)}`;
  const b = `${JSON.stringify(kopruluTeklif(g, BAGDASTIRICILAR))} tasiyan=${kopruluSayi(g, BAGDASTIRICILAR)}`;
  if (a !== b) ayrilan += 1;
  console.log(`${g.kod} ${a}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${ORNEK.length}`);
```

```
G-1 [{"tasiyici":"hava","tutar":4662,"gun":1},{"tasiyici":"kara","tutar":4990,"gun":2}] tasiyan=2
G-2 [{"tasiyici":"hava","tutar":16530,"gun":1},{"tasiyici":"kara","tutar":17239,"gun":2}] tasiyan=2
G-3 [{"tasiyici":"kara","tutar":36236,"gun":4}] tasiyan=1
G-4 [{"tasiyici":"kara","tutar":14009,"gun":4}] tasiyan=1
G-5 [{"tasiyici":"hava","tutar":13170,"gun":1},{"tasiyici":"kara","tutar":17988,"gun":2}] tasiyan=2
ayrilan sonuc = 0 / 5
```

Beş gönderide aynı sıra, aynı tutar, aynı taşıyan sayısı. Kademe dışı ağırlık (G-3) ve
tablosuz posta kodu (G-4) dâhil, iki düzen davranışta ayrılmıyor.

#### Bağlaşımın Ölçülmesi

Ölçü, Tasarım İlkeleri kursundaki Çerçeve Kodunu Uzakta Tutmak dersinin kullandığı ölçünün
aynısıdır: dış sözlükten en az bir ad geçen dosya sayısı. Yanına ikinci bir sütun konuyor —
birim çevirisinin kaç yerde tekrarlandığı.

```js
// sozluk-say.mjs — saglayici sozlugunden ad gecen dosyalari ve birim cevirisini sayar
import { readdirSync, readFileSync } from "node:fs";

const SOZLUK = ["bedelHesapla", "gramaj", "hedefKod", "kurusBedel", "gunAdedi",
  "fiyatSor", "kiloAgirlik", "bolgeAdi", "lira", "aktarmaGunu"];
const CEVRIM = [/\* 1000/g, /lira \* 100/g];

for (const dizin of ["dogrudan", "bagdastirici"]) {
  const dosyalar = readdirSync(dizin).filter((d) => d.endsWith(".mjs")).sort();
  let dokunan = 0;
  console.log(`${dizin}/`);
  for (const d of dosyalar) {
    const metin = readFileSync(`${dizin}/${d}`, "utf8");
    const ad = SOZLUK.filter((a) => new RegExp(`\\b${a}\\b`).test(metin)).length;
    const cevrim = CEVRIM.reduce((t, k) => t + (metin.match(k)?.length ?? 0), 0);
    if (ad > 0) dokunan += 1;
    console.log(`  ${d.padEnd(24)} saglayici adi=${String(ad).padStart(2)}  birim cevirisi=${cevrim}`);
  }
  console.log(`  sozluge dokunan dosya = ${dokunan} / ${dosyalar.length}`);
}
```

```
dogrudan/
  kapsam.mjs               saglayici adi= 6  birim cevirisi=1
  secim.mjs                saglayici adi=10  birim cevirisi=2
  sozluge dokunan dosya = 2 / 2
bagdastirici/
  hava-bagdastirici.mjs    saglayici adi= 5  birim cevirisi=1
  kapsam.mjs               saglayici adi= 0  birim cevirisi=0
  kara-bagdastirici.mjs    saglayici adi= 5  birim cevirisi=1
  kur.mjs                  saglayici adi= 0  birim cevirisi=0
  secim.mjs                saglayici adi= 0  birim cevirisi=0
  sozluge dokunan dosya = 2 / 5
```

İlk düzende iki dosyanın ikisi de sözlüğe dokunuyor; ikincisinde beş dosyanın ikisi ve
dokunan iki dosya bağdaştırıcıların kendisi. Seçim ile kapsam sayımı sıfır ada dokunuyor.
İkinci sütun bağlaşımın türünü de gösteriyor: birim çevirisi ilk düzende üç yerde, ikincide
iki yerde — ve o iki yer aynı sağlayıcının tek çeviri noktasıdır, aynı çevirinin kopyası
değil.

Tasarım İlkeleri kursundaki o ders bağdaştırıcıyı bir **yalıtım** aracı olarak kullanmıştı:
tek bir çerçevenin bağlam nesnesini kitaplığın düz istek biçimine çeviren tek dosya, denetim
ters yönde. Burada durum farklıdır — sağlayıcı sayısı birden çoktur, denetim kitaplıktadır ve
çevirinin amacı yalıtım değil **aynı sözleşme altında karşılaştırılabilirlik**tir. Beş
gönderinin tekliflerinin tek listede sıralanabilmesi bu ortak sözleşmenin sonucudur.

#### Üçüncü Sağlayıcı Eklenince

Sayılar bir değişiklik geldiğinde karşılığını verir. Üçüncü sağlayıcı ilk ikisine de benzemez:
konumsal argüman alır, dizi döndürür ve taşımadığında `undefined` verir. İki ağaç kopyalanıp
değişiklik ikisine de uygulanıyor.

```sh
mkdir -p yeni && cp -r saglayici bolge.mjs dogrudan bagdastirici kosum.mjs yeni/
ls yeni
```

```
bagdastirici
bolge.mjs
dogrudan
kosum.mjs
saglayici
```

```js
// yeni/saglayici/deniz.mjs — ucuncu dis saglayici: konumsal arguman alir, dizi doner, tasimadiginda undefined
const LIMAN = { 34: 1, 35: 1.1 };

export function navlun(agirlikKg, postaKodu) {
  const carpan = LIMAN[postaKodu.slice(0, 2)];
  if (carpan === undefined) return undefined;
  return [Math.round((2600 + agirlikKg * 260) * carpan), 6];
}
```

```js
// yeni/bagdastirici/deniz-bagdastirici.mjs — konumsal arguman ve dizi yanitini ayni sozlesmeye cevirir
import { navlun } from "../saglayici/deniz.mjs";

export const denizBagdastirici = {
  teklif(gonderi) {
    const d = navlun(gonderi.agirlik, gonderi.postaKodu);
    return d === undefined ? null : { tasiyici: "deniz", tutar: d[0], gun: d[1] };
  },
};
```

Bağdaştırıcılı düzende yapılacak tek düzenleme bileşim kökündeki listedir. Doğrudan düzende
ise iki dosyanın gövdesine üçüncü hata biçimi girer. Aşağıdaki betik iki düzenlemeyi de
uygular, iki ağacı koşturur ve farkı sayar; yerinde düzenlemede yedek uzantısı verildiği için
GNU ve BSD `sed` aynı davranır.

```sh
cd yeni
sed -i.y -e 's#^import { bolgeBul } from "../bolge.mjs";#&\nimport { navlun } from "../saglayici/deniz.mjs";#' dogrudan/secim.mjs dogrudan/kapsam.mjs
sed -i.y 's#^  return cikan.sort#  const d = navlun(gonderi.agirlik, gonderi.postaKodu);\n  if (d !== undefined) cikan.push({ tasiyici: "deniz", tutar: d[0], gun: d[1] });\n&#' dogrudan/secim.mjs
sed -i.y 's#^  return sayi;#  if (navlun(gonderi.agirlik, gonderi.postaKodu) !== undefined) sayi += 1;\n&#' dogrudan/kapsam.mjs
sed -i.y -e 's#^import { havaBagdastirici } from "./hava-bagdastirici.mjs";#&\nimport { denizBagdastirici } from "./deniz-bagdastirici.mjs";#' \
         -e 's#havaBagdastirici\];#havaBagdastirici, denizBagdastirici];#' bagdastirici/kur.mjs
rm -f dogrudan/*.y bagdastirici/*.y
node kosum.mjs | head -2
for d in dogrudan bagdastirici; do
  echo "$d"
  echo "  duzenlenen var olan dosya = $(diff -rq "../$d" "$d" | grep -c '^Files ')"
  echo "  yeni dosya                = $(diff -rq "../$d" "$d" | grep -c '^Only in ')"
  echo "  eklenen satir             = $(diff -rN "../$d" "$d" | grep '^>' | grep -cvE '^> *(//|$)')"
done
```

```
G-1 [{"tasiyici":"deniz","tutar":2808,"gun":6},{"tasiyici":"hava","tutar":4662,"gun":1},{"tasiyici":"kara","tutar":4990,"gun":2}] tasiyan=3
G-2 [{"tasiyici":"hava","tutar":16530,"gun":1},{"tasiyici":"kara","tutar":17239,"gun":2}] tasiyan=2
dogrudan
  duzenlenen var olan dosya = 2
  yeni dosya                = 0
  eklenen satir             = 5
bagdastirici
  duzenlenen var olan dosya = 1
  yeni dosya                = 1
  eklenen satir             = 9
```

Doğrudan düzende iki var olan dosya düzenlendi, bağdaştırıcılı düzende bir — ve o bir dosya
bileşim köküdür, iş kuralı taşımaz. Bağdaştırıcılı düzen daha çok satır ekledi (9'a 5),
çünkü yeni sağlayıcının çevirisi kendi dosyasında tam bir nesne olarak duruyor. Açık–Kapalı
İlkesi dersindeki ayrım burada da geçerlidir: eklenen satır yeniden doğrulanmayı gerektirmez,
düzenlenen satır gerektirir. Doğrudan düzende düzenlenen iki gövde, daha önce doğru çalışan
iki sağlayıcının hata yollarını da içeriyordu.

#### Bedel ve Ne Zaman Uygulanmayacağı

Kalıbın bedeli aynı ölçekte sayılabilir.

```sh
for d in dogrudan bagdastirici; do
  echo "$d: $(ls $d/*.mjs | wc -l | tr -d ' ') dosya, $(cat $d/*.mjs | grep -cvE '^ *(//)?$') satir"
done
```

```
dogrudan: 2 dosya, 31 satir
bagdastirici: 5 dosya, 35 satir
```

Üç dosya ve dört satır fazladan; buna bir de dolaylılık basamağı eklenir. Doğrudan düzende
bir teklifin izlenmesi için iki dosya okunur (seçim ve sağlayıcı), bağdaştırıcılı düzende üç
(seçim, bağdaştırıcı, sağlayıcı). Bağdaştırıcı sağlayıcı listesi enjekte edildiği için ithal
grafiğinde de görünmez: `bagdastirici/secim.mjs` hiçbir sağlayıcıyı ithal etmez, bağ çalışma
zamanında bileşim kökünde kurulur. Hata ayıklarken izlenecek çağrı sayısı artar.

Kalıp üç durumda karşılığını vermez. Sağlayıcı **tek** ve sözleşmesi kitaplığın denetimindeyse
çevirinin ayrı dosyaya çıkarılması yalnız bir dosya ekler. Sağlayıcının sözlüğü kitaplığın
sözlüğüyle zaten örtüşüyorsa bağdaştırıcı gövdesi alan adlarını birebir kopyalayan bir katmana
düşer. Üçüncüsü daha inceden ısırır: bağdaştırıcı yalnız eşlediği kadarını geçirir. Bir
sağlayıcının sunduğu ama sözleşmede karşılığı olmayan yetenek — parçalı gönderi, teslim
saati aralığı — iş kuralı tarafından kullanılamaz; kullanılacaksa önce ortak sözleşmenin
büyütülmesi ve bütün bağdaştırıcıların o alana bir yanıt vermesi gerekir. Ortak sözleşmenin
maliyeti sağlayıcı sayısıyla doğru orantılı büyür.

#### Özet

- Bağdaştırıcının çözdüğü problem, kitaplığın belirlemediği bir arayüzle kitaplığın kendi
  sözleşmesi arasındaki uyumsuzluktur; çözüm, çeviriyi sağlayıcı başına tek bir nesnede
  toplamaktır.
- Çeviri üç şeyi kapsar: alan adları, birimler ve "taşımıyorum" ifadesinin biçimi — sonuncusu
  `null` ile hata arasındaki fark olduğu için en çok kopyalanan bilgidir.
- Dış sözlüğe dokunan dosya sayısı doğrudan düzende 2/2, bağdaştırıcılı düzende 2/5'tir ve
  dokunan iki dosya bağdaştırıcıların kendisidir; birim çevirisi 3 yerden 2 yere iner.
- Üçüncü sağlayıcı eklenince doğrudan düzende 2 var olan dosya düzenlendi, bağdaştırıcılı
  düzende 1 (bileşim kökü) düzenlendi ve 1 yeni dosya eklendi; beş gönderinin yanıtları iki
  düzende de aynı kaldı.
- Bedel 3 dosya, 4 satır ve bir dolaylılık basamağıdır; tek sağlayıcılı, sözlüğü örtüşen ya
  da yeteneklerinin ortak sözleşmeye sığmadığı durumlarda bu bedel karşılıksız kalır.

#### Sonraki Adım

Bağdaştırıcı var olan bir arayüzü sonradan uydurdu: sağlayıcılar zaten yazılmıştı, kitaplığın
sözleşmesi de. Kalıp ikisinin arasına girdi. Bir sonraki problem baştan tasarım aşamasında
çıkar. Kitaplık ücret raporu üretecekse iki bağımsız eksen vardır: raporun **türü** (özet,
kademe dökümü, bölge dökümü) ve raporun **biçimi** (düz metin, ayrılmış değer, hizalanmış
tablo). İki eksen kalıtımla birleştirilirse tür sayısı ile biçim sayısının çarpımı kadar sınıf
doğar. Sonraki ders bu çarpımı üç tür ve iki biçim için sayar, iki ekseni ayrı hiyerarşilere
bölen köprü kalıbını yazar ve üçüncü bir biçim eklendiğinde iki düzende eklenen tip ile
eklenen satırı karşılaştırır.

### Köprü

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/kopru

Soyutlama ile gerçekleştirimin ayrı evrimi: rapor türü ve çıktı biçimi eksenlerinin kalıtımla çarpıldığı düzen ile iki hiyerarşiye bölündüğü düzenin tip, satır ve çıktı gövdesi sayısıyla karşılaştırılması, üçüncü biçim eklendiğinde eklenen tip ve satırın ölçülmesi, çarpımın toplama inmesinin bedeli.

Bağdaştırıcı var olan iki arayüzün arasına sonradan girdi: sağlayıcılar da kitaplığın
sözleşmesi de yazılmıştı, kalıp ikisini uzlaştırdı. Bu dersin problemi tasarım aşamasında,
henüz hiçbir şey yazılmamışken belirir: bir yetenek **iki bağımsız eksende** birden değişiyor.

Kitaplık ücret raporu üretecek. Raporun **türü** bir eksendir — özet, ağırlık kademesi
dökümü, bölge dökümü — ve iş kuralı değiştikçe yeni tür istenir. Raporun **çıktı biçimi**
ikinci eksendir: düz metin, ayrılmış değer, hizalanmış tablo. İki eksen birbirinden habersiz
büyür; hiçbir biçim belirli bir türe bağlı değildir. **Köprü** (bridge) kalıbı bu iki ekseni
ayrı hiyerarşilere böler ve aralarına bir sözleşme koyar. Kazanç tip sayısında ölçülür:
kalıtımla birleştirmede tür sayısı ile biçim sayısının **çarpımı**, köprüde **toplamı**.

#### Problem: Aynı Veri, Ayrı Biçim

Raporlanan gönderi kümesi ve gruplama yardımcısı iki düzende de ortaktır; karşılaştırılan
eksenler bunlar değildir.

```js
// veri.mjs — iki duzenin de raporladigi gonderi kumesi; tutar kurustur
const KADEME = [[1, 4990], [5, 8490], [15, 14990], [30, 24990]];
const KATSAYI = { 34: 100, "06": 115, 35: 120, 65: 145 };
export const BOLGE = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" };

const tutar = (agirlik, postaKodu) => {
  const kademe = KADEME.find(([ust]) => agirlik <= ust) ?? [0, 24990];
  return Math.round((kademe[1] * (KATSAYI[postaKodu.slice(0, 2)] ?? 165)) / 100);
};

export const GONDERILER = [
  ["G-1", 0.8, "34100"], ["G-2", 12, "06500"], ["G-3", 26, "65100"],
  ["G-4", 3, "35400"], ["G-5", 4.5, "34710"], ["G-6", 18, "06800"],
].map(([kod, agirlik, postaKodu]) => ({ kod, agirlik, postaKodu, tutar: tutar(agirlik, postaKodu) }));

export const KADEME_SIRA = ["0-1 kg", "1-5 kg", "5-15 kg", "15+ kg"];

export const kademeAdi = (agirlik) =>
  agirlik <= 1 ? "0-1 kg" : agirlik <= 5 ? "1-5 kg" : agirlik <= 15 ? "5-15 kg" : "15+ kg";

export const grupla = (gonderiler, anahtar, sira = null) => {
  const kume = new Map();
  for (const g of gonderiler) {
    const a = anahtar(g), v = kume.get(a) ?? [0, 0];
    kume.set(a, [v[0] + 1, v[1] + g.tutar]);
  }
  const satirlar = [...kume].map(([a, v]) => [a, String(v[0]), String(v[1])]);
  return sira === null ? satirlar.sort() : satirlar.sort((x, y) => sira.indexOf(x[0]) - sira.indexOf(y[0]));
};
```

#### Çarpımın Sınıflara Açıldığı Düzen

İlk düzende her tür–biçim çifti bir yaprak sınıftır. Veri çıkarımı ara sınıflarda toplanır,
çıktıyı üreten `yaz` yöntemi yapraklara düşer.

```js
// kalitim/raporlar.mjs — her tur-bicim cifti icin bir yaprak sinif: 3 x 2 = 6
import { BOLGE, KADEME_SIRA, grupla, kademeAdi } from "../veri.mjs";

class Rapor {
  constructor(gonderiler) { this.gonderiler = gonderiler; }
}

export class OzetRapor extends Rapor {
  baslik() { return "ozet"; }
  sutunlar() { return ["olcut", "deger"]; }
  satirlar() {
    const t = this.gonderiler.reduce((s, g) => s + g.tutar, 0);
    return [["gonderi", String(this.gonderiler.length)], ["toplam", String(t)],
      ["ortalama", String(Math.round(t / this.gonderiler.length))]];
  }
}
export class OzetMetin extends OzetRapor {
  yaz() {
    return [this.baslik(), this.sutunlar().join("  "),
      ...this.satirlar().map((s) => s.join("  "))].join("\n");
  }
}
export class OzetAyrilmis extends OzetRapor {
  yaz() {
    return [this.sutunlar().join(";"), ...this.satirlar().map((s) => s.join(";"))].join("\n");
  }
}

export class KademeRapor extends Rapor {
  baslik() { return "kademe dokumu"; }
  sutunlar() { return ["kademe", "adet", "tutar"]; }
  satirlar() { return grupla(this.gonderiler, (g) => kademeAdi(g.agirlik), KADEME_SIRA); }
}
export class KademeMetin extends KademeRapor {
  yaz() {
    return [this.baslik(), this.sutunlar().join("  "),
      ...this.satirlar().map((s) => s.join("  "))].join("\n");
  }
}
export class KademeAyrilmis extends KademeRapor {
  yaz() {
    return [this.sutunlar().join(";"), ...this.satirlar().map((s) => s.join(";"))].join("\n");
  }
}

export class BolgeRapor extends Rapor {
  baslik() { return "bolge dokumu"; }
  sutunlar() { return ["bolge", "adet", "tutar"]; }
  satirlar() { return grupla(this.gonderiler, (g) => BOLGE[g.postaKodu.slice(0, 2)] ?? "bilinmeyen"); }
}
export class BolgeMetin extends BolgeRapor {
  yaz() {
    return [this.baslik(), this.sutunlar().join("  "),
      ...this.satirlar().map((s) => s.join("  "))].join("\n");
  }
}
export class BolgeAyrilmis extends BolgeRapor {
  yaz() {
    return [this.sutunlar().join(";"), ...this.satirlar().map((s) => s.join(";"))].join("\n");
  }
}
```

Altı yaprağın gövdeleri ikişer ikişer birebir aynıdır. Bu, Temiz Kod kursundaki bilgi
yinelemesinin tam örneğidir: ayrılmış değer biçiminin nasıl üretildiği bilgisi üç ayrı sınıfta
duruyor. Çağıran taraf çifti bir ada çevirir.

```js
// kalitim/kur.mjs — bilesim koku: tur-bicim cifti dogrudan bir yaprak sinifa esler
import { OzetMetin, OzetAyrilmis, KademeMetin, KademeAyrilmis, BolgeMetin, BolgeAyrilmis } from "./raporlar.mjs";

const YAPRAK = {
  "ozet/metin": OzetMetin, "ozet/ayrilmis": OzetAyrilmis,
  "kademe/metin": KademeMetin, "kademe/ayrilmis": KademeAyrilmis,
  "bolge/metin": BolgeMetin, "bolge/ayrilmis": BolgeAyrilmis,
};

export const rapor = (tur, bicim, gonderiler) => new YAPRAK[`${tur}/${bicim}`](gonderiler);
```

#### Çözüm: İki Hiyerarşi, Aralarında Bir Sözleşme

Köprünün çözümü, ikinci ekseni kalıtım ağacından çıkarıp kendi ağacına koymak ve soyutlamanın
gerçekleştirime bir **başvuru** tutmasıdır. Sözleşme üç veriden oluşur: başlık, sütun adları,
satırlar.

```js
// kopru/bicimler.mjs — gerceklestirim hiyerarsisi: cikti bicimi burada, rapor turu bilinmez
export const metinBicimi = {
  uret(baslik, sutunlar, satirlar) {
    return [baslik, sutunlar.join("  "), ...satirlar.map((s) => s.join("  "))].join("\n");
  },
};

export const ayrilmisBicimi = {
  uret(baslik, sutunlar, satirlar) {
    return [sutunlar.join(";"), ...satirlar.map((s) => s.join(";"))].join("\n");
  },
};
```

```js
// kopru/raporlar.mjs — soyutlama hiyerarsisi: veri cikarimi burada, cikti bicimi bicim nesnesinde
import { BOLGE, KADEME_SIRA, grupla, kademeAdi } from "../veri.mjs";

class Rapor {
  constructor(gonderiler, bicim) {
    this.gonderiler = gonderiler;
    this.bicim = bicim;
  }
  yaz() { return this.bicim.uret(this.baslik(), this.sutunlar(), this.satirlar()); }
}

export class OzetRapor extends Rapor {
  baslik() { return "ozet"; }
  sutunlar() { return ["olcut", "deger"]; }
  satirlar() {
    const t = this.gonderiler.reduce((s, g) => s + g.tutar, 0);
    return [["gonderi", String(this.gonderiler.length)], ["toplam", String(t)],
      ["ortalama", String(Math.round(t / this.gonderiler.length))]];
  }
}

export class KademeRapor extends Rapor {
  baslik() { return "kademe dokumu"; }
  sutunlar() { return ["kademe", "adet", "tutar"]; }
  satirlar() { return grupla(this.gonderiler, (g) => kademeAdi(g.agirlik), KADEME_SIRA); }
}

export class BolgeRapor extends Rapor {
  baslik() { return "bolge dokumu"; }
  sutunlar() { return ["bolge", "adet", "tutar"]; }
  satirlar() { return grupla(this.gonderiler, (g) => BOLGE[g.postaKodu.slice(0, 2)] ?? "bilinmeyen"); }
}
```

Yaprak sınıf kalmadı: çift artık bir sınıf adı değil, iki nesnenin bileşimidir ve bileşim
kökünde kurulur.

```js
// kopru/kur.mjs — bilesim koku: iki eksen burada birlestirilir
import { OzetRapor, KademeRapor, BolgeRapor } from "./raporlar.mjs";
import { metinBicimi, ayrilmisBicimi } from "./bicimler.mjs";

const RAPOR = { ozet: OzetRapor, kademe: KademeRapor, bolge: BolgeRapor };
const BICIM = { metin: metinBicimi, ayrilmis: ayrilmisBicimi };

export const rapor = (tur, bicim, gonderiler) => new RAPOR[tur](gonderiler, BICIM[bicim]);
```

```js
// kosum.mjs — iki duzen alti tur-bicim ciftinde ayni metni uretiyor mu
import { GONDERILER } from "./veri.mjs";
import { rapor as kalitimRapor } from "./kalitim/kur.mjs";
import { rapor as kopruRapor } from "./kopru/kur.mjs";

let ayrilan = 0;
for (const tur of ["ozet", "kademe", "bolge"]) {
  for (const bicim of ["metin", "ayrilmis"]) {
    if (kalitimRapor(tur, bicim, GONDERILER).yaz() !== kopruRapor(tur, bicim, GONDERILER).yaz()) ayrilan += 1;
  }
}
console.log(kalitimRapor("bolge", "metin", GONDERILER).yaz());
console.log("--");
console.log(kopruRapor("bolge", "ayrilmis", GONDERILER).yaz());
console.log(`ayrilan cift = ${ayrilan} / 6`);
```

```
bolge dokumu
bolge  adet  tutar
orta  3  56166
uzak  1  36236
yakin  2  13480
--
bolge;adet;tutar
orta;3;56166
uzak;1;36236
yakin;2;13480
ayrilan cift = 0 / 6
```

Altı çiftin altısında aynı metin. Fark çıktıda değil, çıktının kaç tipe bölündüğünde.

#### Tip ve Gövde Sayısının Ölçülmesi

```js
// tip-say.mjs — iki duzendeki tip sayisini, satir sayisini ve cikti govdesi sayisini sayar
import { readdirSync, readFileSync } from "node:fs";

const say = (metin, kalip) => (metin.match(kalip) ?? []).length;

for (const dizin of ["kalitim", "kopru"]) {
  let tip = 0, govde = 0, satir = 0;
  for (const d of readdirSync(dizin).sort()) {
    const metin = readFileSync(`${dizin}/${d}`, "utf8");
    tip += say(metin, /^(?:export )?class \w+/gm) + say(metin, /^export const \w+Bicimi/gm);
    govde += say(metin, /^  yaz\(\)/gm) + say(metin, /^  uret\(/gm);
    satir += metin.split("\n").filter((s) => s.trim() !== "" && !s.trim().startsWith("//")).length;
  }
  console.log(`${dizin.padEnd(8)} tip=${String(tip).padStart(2)}  satir=${String(satir).padStart(3)}  cikti govdesi=${govde}`);
}
```

```
kalitim  tip=10  satir= 63  cikti govdesi=6
kopru    tip= 6  satir= 42  cikti govdesi=3
```

Üç tür ve iki biçim için kalıtım düzeni 10 tip taşıyor: bir taban, üç ara sınıf, altı yaprak.
Köprü düzeni 6 tip taşıyor: bir taban, üç rapor, iki biçim nesnesi. Çıktı gövdesi sayısı
6'ya 3 — köprüdeki üçün biri devir gövdesi (`yaz`), ikisi biçimin kendisi. Aynı biçim gövdesi
kalıtım düzeninde tür sayısı kadar, yani 3 kez yazılmıştır.

#### Üçüncü Biçim Eklenince

Yeni gereksinim hizalanmış tablodur: sütunlar en geniş değere göre doldurulur. Bu, ikinci
eksende bir eklemedir; hiçbir rapor türünün veri çıkarımını ilgilendirmez.

```sh
mkdir -p yeni && cp -r veri.mjs kalitim kopru kosum.mjs tip-say.mjs yeni/
ls yeni
```

```
kalitim
kopru
kosum.mjs
tip-say.mjs
veri.mjs
```

Kalıtım düzeninde biçim gövdesinin üç kez yazılmasını önlemenin yolu onu bir yardımcıya
çıkarmaktır — ve o yardımcıya bir ad ile bir sözleşme verildiği anda köprünün gerçekleştirim
tarafı yazılmış olur. Fark, yaprak sınıfların yine gerekmesidir.

```js
// yeni/kalitim/hizali.mjs — ucuncu bicim: govde bir kez yazildi ama uc yaprak sinif gerekti
import { OzetRapor, KademeRapor, BolgeRapor } from "./raporlar.mjs";

const hizala = (baslik, sutunlar, satirlar) => {
  const en = sutunlar.map((s, i) => Math.max(s.length, ...satirlar.map((r) => r[i].length)));
  const satir = (r) => r.map((h, i) => h.padEnd(en[i])).join(" | ").trimEnd();
  return [baslik, satir(sutunlar), ...satirlar.map(satir)].join("\n");
};

export class OzetHizali extends OzetRapor {
  yaz() { return hizala(this.baslik(), this.sutunlar(), this.satirlar()); }
}
export class KademeHizali extends KademeRapor {
  yaz() { return hizala(this.baslik(), this.sutunlar(), this.satirlar()); }
}
export class BolgeHizali extends BolgeRapor {
  yaz() { return hizala(this.baslik(), this.sutunlar(), this.satirlar()); }
}
```

Köprü düzeninde eklenen şey gerçekleştirim hiyerarşisine bir nesnedir.

```sh
cat >> yeni/kopru/bicimler.mjs <<'BICIM'

export const hizaliBicimi = {
  uret(baslik, sutunlar, satirlar) {
    const en = sutunlar.map((s, i) => Math.max(s.length, ...satirlar.map((r) => r[i].length)));
    const satir = (r) => r.map((h, i) => h.padEnd(en[i])).join(" | ").trimEnd();
    return [baslik, satir(sutunlar), ...satirlar.map(satir)].join("\n");
  },
};
BICIM
tail -3 yeni/kopru/bicimler.mjs
```

```
    return [baslik, satir(sutunlar), ...satirlar.map(satir)].join("\n");
  },
};
```

Geriye iki bileşim kökünün ve koşum betiğinin güncellenmesi kalıyor; aşağıdaki betik bunları
uygular, dokuz çifti koşturur ve iki düzeni sayar.

```sh
cd yeni
sed -i.y -e 's#^import { OzetMetin.*$#&\nimport { OzetHizali, KademeHizali, BolgeHizali } from "./hizali.mjs";#' \
         -e 's#^  "bolge/metin": BolgeMetin, "bolge/ayrilmis": BolgeAyrilmis,#&\n  "ozet/hizali": OzetHizali, "kademe/hizali": KademeHizali, "bolge/hizali": BolgeHizali,#' kalitim/kur.mjs
sed -i.y -e 's#, ayrilmisBicimi } from "./bicimler.mjs";#, ayrilmisBicimi, hizaliBicimi } from "./bicimler.mjs";#' \
         -e 's#ayrilmis: ayrilmisBicimi };#ayrilmis: ayrilmisBicimi, hizali: hizaliBicimi };#' kopru/kur.mjs
sed -i.y -e 's#\["metin", "ayrilmis"\]#["metin", "ayrilmis", "hizali"]#' -e 's#/ 6`#/ 9`#' \
         -e 's#kopruRapor("bolge", "ayrilmis"#kopruRapor("kademe", "hizali"#' kosum.mjs
rm -f kalitim/*.y kopru/*.y *.y
node kosum.mjs | tail -7
node tip-say.mjs
for d in kalitim kopru; do
  echo "$d: eklenen satir=$(diff -rN "../$d" "$d" | grep '^>' | grep -cvE '^> *(//|$)')  duzenlenen dosya=$(diff -rq "../$d" "$d" | grep -c '^Files ')"
done
```

```
kademe dokumu
kademe  | adet | tutar
0-1 kg  | 1    | 4990
1-5 kg  | 2    | 18678
5-15 kg | 1    | 17239
15+ kg  | 2    | 64975
ayrilan cift = 0 / 9
kalitim  tip=13  satir= 80  cikti govdesi=9
kopru    tip= 7  satir= 49  cikti govdesi=4
kalitim: eklenen satir=17  duzenlenen dosya=1
kopru: eklenen satir=9  duzenlenen dosya=2
```

Dokuz çift de aynı metni üretti. Kalıtım düzenine 3 tip ve 17 satır girdi, köprü düzenine
1 tip ve 9 satır. İki ölçüm noktası çarpımı gösteriyor: kalıtım 10 → 13 (üç tür × üç biçim
artı taban ve ara sınıflar), köprü 6 → 7. Dördüncü biçim kalıtımda 3 tip daha, köprüde 1 tip
daha getirir; fark biçim sayısıyla değil, **tür sayısı kadar** büyür.

Düzenlenen dosya sayısında köprü geride: 2'ye 1. Sebep, köprünün yeni biçimi var olan
gerçekleştirim dosyasına eklemesidir; kalıtım ise yeni dosya açtı. Bu iki sayı birlikte
okunur — kalıtımda düzenlenen tek dosya bileşim köküne üç satır eklerken, köprüde düzenlenen
iki dosyanın ikisi de tek satırlık listelerdir.

#### Bedel ve Ne Zaman Uygulanmayacağı

Köprünün ilk bedeli sözleşmenin kendisidir. Soyutlama ile gerçekleştirim arasındaki üç veri —
başlık, sütunlar, satırlar — bütün biçimlerin yetinmek zorunda olduğu dildir. Sütun genişliğine
göre satır kırmak, alt toplam eklemek ya da hücreye para birimi yazmak isteyen bir biçim bu
sözleşmeye sığmaz; sözleşmenin büyütülmesi gerekir ve büyüyen sözleşme, **bütün** biçim
nesnelerinin o alana bir yanıt vermesini zorunlu kılar. Maliyet gerçekleştirim sayısıyla doğru
orantılı büyür.

İkinci bedel dolaylılıktır. Kalıtım düzeninde bir çıktı tek gövdede üretilir; köprüde çağrı
`yaz` yönteminden `uret` yöntemine geçer, izlenecek gövde sayısı 1'den 2'ye çıkar. Üçüncüsü
hücreye özel davranışın kaybıdır: kalıtım düzeninde altı yaprağın herhangi biri tek başına
özelleştirilebilir — "bölge raporunun ayrılmış değer çıktısında başlık satırı olmasın" isteği
tek bir yaprağın gövdesini değiştirir. Köprüde aynı istek ya biçim nesnesine rapor türüne
bakan bir koşul koyar — ki bu gerçekleştirimi soyutlamaya bağlar ve iki eksenin bağımsızlığını
bozar — ya da sözleşmeye bir bayrak ekler.

Buradan iki uygulanmama koşulu çıkar. Eksenlerden biri **tek üyeli** kalacaksa çarpım zaten
toplama eşittir ve köprü yalnız bir dolaylılık katmanıdır. Hücrelerin çoğu özel davranış
istiyorsa — yani biçim gerçekten türe bağlıysa — eksenler bağımsız değildir; o durumda köprü
her hücrede koşul biriktirir ve kalıtım düzeni daha az koşulla çalışır.

#### Özet

- Köprünün çözdüğü problem, bir yeteneğin iki bağımsız eksende birden değişmesidir; çözüm,
  ikinci ekseni ayrı bir hiyerarşiye alıp soyutlamanın gerçekleştirime başvuru tutmasıdır.
- Üç tür ve iki biçim için kalıtım düzeni 10 tip ve 63 satır, köprü düzeni 6 tip ve 42 satır
  taşır; çıktı gövdesi 6'ya 3'tür ve aynı biçim gövdesi kalıtımda tür sayısı kadar yazılır.
- Üçüncü biçim kalıtım düzenine 3 tip ile 17 satır, köprü düzenine 1 tip ile 9 satır ekledi;
  dokuz tür–biçim çiftinin çıktısı iki düzende de aynı kaldı.
- İki ölçüm noktası büyüme kuralını doğruluyor: kalıtımda tip sayısı tür ile biçimin çarpımı,
  köprüde toplamı kadar artar.
- Bedel, bütün gerçekleştirimlerin yetinmek zorunda olduğu sabit bir sözleşme, bir dolaylılık
  basamağı ve hücreye özel davranışın kaybıdır; eksenlerden biri tek üyeliyse ya da biçim
  gerçekten türe bağlıysa kalıp karşılığını vermez.

#### Sonraki Adım

Bağdaştırıcı ile köprü, tek bir nesnenin arkasına başka bir nesne koydu; ikisinde de sarılan
şey birdi. Raporlanan gönderi listesi de düzdü: altı gönderi, hepsi aynı düzeyde. Oysa alan
bunu bozar — bir gönderi, içinde başka gönderiler taşıyan **konsolide** bir gönderi olabilir
ve o gönderilerden biri yine konsolide olabilir. Ağırlık toplamı, ücret toplamı ve teslim
süresi artık bir ağaç üzerinde hesaplanır. Her işlem "bu tek parça mı, konsolide mi" sorusunu
kendi gövdesinde sorarsa, tür sınaması işlem sayısı kadar tekrarlanır. Sonraki ders bu
sınamaları sayar, yaprakla düğümü aynı arayüzün arkasına koyan bileşik kalıbını yazar ve iki
düzeni iki ayrı genişleme yönünde ölçer: yeni bir düğüm türü ve yeni bir işlem eklendiğinde
dokunulan dosya, eklenen satır ve tür sınaması sayısı.

### Bileşik

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/bilesik

Ağaç yapılarının tek biçim ele alınması: konsolide gönderi ağacında dört hesabın tür sınamasını her gövdede tekrarladığı düzen ile yaprak ve düğümü aynı arayüzün arkasına koyan düzenin sınama sayısıyla karşılaştırılması, yeni düğüm türü ve yeni işlem eklendiğinde dokunulan dosyanın ölçülmesi, şeffaflık ile güvenlik arasındaki seçim.

Köprü iki bağımsız ekseni ayırdı, ama raporladığı gönderi listesi düzdü: altı gönderi, hepsi
aynı düzeyde. Alan bunu bozar. Bir gönderi, içinde başka gönderiler taşıyan **konsolide** bir
gönderi olabilir; onun parçalarından biri yine konsolide olabilir. Ağırlık toplamı, ücret,
teslim süresi ve parça sayısı artık bir ağaç üzerinde hesaplanır.

Sorun, her hesabın "bu tek parça mı, konsolide mi" sorusunu kendi gövdesinde sormasıdır. Dört
hesap varsa aynı tür sınaması dört kez yazılır ve beşinci hesap beşinci kopyayı getirir.
**Bileşik** (composite) kalıbı yaprağı ve düğümü aynı arayüzün arkasına koyarak sınamayı
hesaplardan çıkarır. Ölçü tür sınaması sayısıdır; kalıp iki genişleme yönünde ayrı ayrı
sınanacaktır — yeni bir düğüm türü ve yeni bir işlem.

#### Problem: Ağacın Şekli Her Hesaba Sızıyor

Ağaç ve tarife iki düzende de ortaktır. Konsolide düğümün kendi ambalaj ağırlığı, yaprağın
posta koduna göre kademe ücreti ve bölgeye göre teslim günü vardır.

```js
// agac.mjs — iki duzenin de hesapladigi gonderi agaci ve ortak tarife
const KADEME = [[1, 4990], [5, 8490], [15, 14990], [30, 24990]];
const KATSAYI = { 34: 100, "06": 115, 35: 120, 65: 145 };
const GUN = { 34: 1, "06": 2, 35: 2, 65: 4 };

export const kademeUcret = (agirlik, postaKodu) => {
  const k = KADEME.find(([ust]) => agirlik <= ust) ?? [0, 24990];
  return Math.round((k[1] * (KATSAYI[postaKodu.slice(0, 2)] ?? 165)) / 100);
};

export const bolgeGun = (postaKodu) => GUN[postaKodu.slice(0, 2)] ?? 5;

export const AGAC = {
  tip: "konsolide", kod: "K-1", ambalaj: 0.5, parcalar: [
    { tip: "koli", kod: "G-1", agirlik: 0.8, postaKodu: "34100" },
    { tip: "konsolide", kod: "K-2", ambalaj: 0.3, parcalar: [
      { tip: "koli", kod: "G-2", agirlik: 12, postaKodu: "06500" },
      { tip: "koli", kod: "G-3", agirlik: 3, postaKodu: "35400" },
    ] },
    { tip: "koli", kod: "G-4", agirlik: 18, postaKodu: "06800" },
  ],
};
```

İlk düzende dört hesap dört ayrı dosyadır ve dördü de aynı sınamayla başlar.

```js
// sinama/agirlik.mjs — her islem tur sinamasini kendi govdesinde tekrarlar
export function agirlik(g) {
  if (g.tip === "konsolide") return g.ambalaj + g.parcalar.reduce((t, p) => t + agirlik(p), 0);
  return g.agirlik;
}
```

```js
// sinama/ucret.mjs — konsolide gonderide uc parcadan itibaren yuzde bes hacim indirimi
import { kademeUcret } from "../agac.mjs";

export function ucret(g) {
  if (g.tip === "konsolide") {
    const toplam = g.parcalar.reduce((t, p) => t + ucret(p), 0);
    return g.parcalar.length >= 3 ? Math.round(toplam * 0.95) : toplam;
  }
  return kademeUcret(g.agirlik, g.postaKodu);
}
```

```js
// sinama/sure.mjs — konsolide gonderi en yavas parcasi kadar, arti bir aktarma gunu
import { bolgeGun } from "../agac.mjs";

export function sure(g) {
  if (g.tip === "konsolide") return Math.max(...g.parcalar.map(sure)) + 1;
  return bolgeGun(g.postaKodu);
}
```

```js
// sinama/parca.mjs — agactaki yaprak sayisi
export function parcaSayisi(g) {
  if (g.tip === "konsolide") return g.parcalar.reduce((t, p) => t + parcaSayisi(p), 0);
  return 1;
}
```

Dört dosyanın dördü de iki şeyi birden biliyor: hesabın kendisini ve ağacın nasıl gezildiğini.
İkinci bilgi dördünde de aynıdır.

#### Çözüm: Yaprak ile Düğüm Aynı Arayüzde

Bileşiğin çözümü, ağacı gezme bilgisini düğümün kendisine vermektir. Ortak arayüz dört
yöntemden oluşur ve yaprak da düğüm de bu dördünü karşılar.

```js
// bilesik/koli.mjs — yaprak: dort islemi de kendisi yanitlar
import { kademeUcret, bolgeGun } from "../agac.mjs";

export class Koli {
  constructor({ kod, agirlik, postaKodu }) {
    this.kod = kod;
    this.kilo = agirlik;
    this.postaKodu = postaKodu;
  }
  agirlik() { return this.kilo; }
  ucret() { return kademeUcret(this.kilo, this.postaKodu); }
  sure() { return bolgeGun(this.postaKodu); }
  parcaSayisi() { return 1; }
}
```

```js
// bilesik/konsolide.mjs — dugum: parcalarina ayni arayuzle sorar, turlerini bilmez
export class Konsolide {
  constructor({ kod, ambalaj, parcalar }) {
    this.kod = kod;
    this.ambalaj = ambalaj;
    this.parcalar = parcalar;
  }
  agirlik() { return this.ambalaj + this.parcalar.reduce((t, p) => t + p.agirlik(), 0); }
  ucret() {
    const toplam = this.parcalar.reduce((t, p) => t + p.ucret(), 0);
    return this.parcalar.length >= 3 ? Math.round(toplam * 0.95) : toplam;
  }
  sure() { return Math.max(...this.parcalar.map((p) => p.sure())) + 1; }
  parcaSayisi() { return this.parcalar.reduce((t, p) => t + p.parcaSayisi(), 0); }
}
```

Düğümün gövdesinde `p` için hiçbir tür sınaması yok: parça yaprak da olabilir, başka bir
konsolide de. Sınama tek bir yere çekilmiştir — veriden nesne ağacını kuran yere.

```js
// bilesik/kur.mjs — tur sinamasinin kaldigi tek yer: veriden nesne agacini kurar
import { Koli } from "./koli.mjs";
import { Konsolide } from "./konsolide.mjs";

const DUGUM = { konsolide: Konsolide };

export const kur = (veri) => veri.tip === "koli"
  ? new Koli(veri)
  : new DUGUM[veri.tip]({ ...veri, parcalar: veri.parcalar.map(kur) });
```

```js
// kosum.mjs — iki duzen ayni agac icin ayni dort sonucu veriyor mu
import { AGAC } from "./agac.mjs";
import { agirlik } from "./sinama/agirlik.mjs";
import { ucret } from "./sinama/ucret.mjs";
import { sure } from "./sinama/sure.mjs";
import { parcaSayisi } from "./sinama/parca.mjs";
import { kur } from "./bilesik/kur.mjs";

const n = kur(AGAC);
const a = [agirlik(AGAC).toFixed(2), ucret(AGAC), sure(AGAC), parcaSayisi(AGAC)];
const b = [n.agirlik().toFixed(2), n.ucret(), n.sure(), n.parcaSayisi()];
console.log(`sinama : agirlik=${a[0]} kg  ucret=${a[1]}  sure=${a[2]} gun  parca=${a[3]}`);
console.log(`bilesik: agirlik=${b[0]} kg  ucret=${b[1]}  sure=${b[2]} gun  parca=${b[3]}`);
console.log(`ayrilan sonuc = ${a.filter((v, i) => String(v) !== String(b[i])).length} / 4`);
```

```
sinama : agirlik=34.60 kg  ucret=58098  sure=4 gun  parca=4
bilesik: agirlik=34.60 kg  ucret=58098  sure=4 gun  parca=4
ayrilan sonuc = 0 / 4
```

İki düzey derinlikte, üç yaprak ve bir iç düğümle dört hesabın dördü aynı. Fark sınamaların
sayısında.

```js
// olc.mjs — tur sinamasi sayisini ve agacin ic seklini bilen dosya sayisini olcer
import { readdirSync, readFileSync } from "node:fs";

for (const dizin of ["sinama", "bilesik"]) {
  const dosyalar = readdirSync(dizin).filter((d) => d.endsWith(".mjs")).sort();
  let sinama = 0, sekil = 0, satir = 0;
  for (const d of dosyalar) {
    const metin = readFileSync(`${dizin}/${d}`, "utf8");
    sinama += (metin.match(/tip === /g) ?? []).length;
    if (/\bparcalar\b/.test(metin)) sekil += 1;
    satir += metin.split("\n").filter((s) => s.trim() !== "" && !s.trim().startsWith("//")).length;
  }
  console.log(`${dizin.padEnd(8)} dosya=${dosyalar.length}  tur sinamasi=${sinama}  ` +
    `agac seklini bilen dosya=${sekil}  satir=${satir}`);
}
```

```
sinama   dosya=4  tur sinamasi=4  agac seklini bilen dosya=4  satir=21
bilesik  dosya=3  tur sinamasi=1  agac seklini bilen dosya=2  satir=32
```

Tür sınaması 4'ten 1'e indi; ağacın iç şeklini (`parcalar` alanını) bilen dosya sayısı 4'ten
2'ye. Bileşik düzen 11 satır daha uzun: kalıbın peşin bedeli bu.

#### Yeni Bir Düğüm Türü Eklenince

İlk genişleme yönü yeni bir düğüm türüdür. Palet, konsolideden üç noktada ayrılır: 25 kg dara
ağırlığı, sabit 3500 kuruş palet ücreti ve iki günlük aktarma. Ağaca bir palet düğümü giriyor.

```sh
mkdir -p yeni-tur && cp -r agac.mjs sinama bilesik kosum.mjs olc.mjs yeni-tur/
ls yeni-tur
```

```
agac.mjs
bilesik
kosum.mjs
olc.mjs
sinama
```

```js
// yeni-tur/bilesik/palet.mjs — ikinci dugum turu: dara agirligi, sabit palet ucreti, iki gun aktarma
export class Palet {
  constructor({ kod, parcalar }) {
    this.kod = kod;
    this.parcalar = parcalar;
  }
  agirlik() { return 25 + this.parcalar.reduce((t, p) => t + p.agirlik(), 0); }
  ucret() { return 3500 + this.parcalar.reduce((t, p) => t + p.ucret(), 0); }
  sure() { return Math.max(...this.parcalar.map((p) => p.sure())) + 2; }
  parcaSayisi() { return this.parcalar.reduce((t, p) => t + p.parcaSayisi(), 0); }
}
```

Sınama düzeninde ise dört hesabın dördüne birer dal girer. Aşağıdaki betik ağaca paleti koyar,
dört dosyayı da düzenler, düğüm kaydına yeni türü yazar ve iki düzeni sayar.

```sh
cd yeni-tur
sed -i.y 's#    { tip: "koli", kod: "G-4", agirlik: 18, postaKodu: "06800" },#    { tip: "palet", kod: "P-1", parcalar: [\n      { tip: "koli", kod: "G-4", agirlik: 18, postaKodu: "06800" },\n      { tip: "koli", kod: "G-5", agirlik: 6, postaKodu: "65100" },\n    ] },#' agac.mjs
sed -i.y 's#^  if (g.tip === "konsolide") return g.ambalaj#  if (g.tip === "palet") return 25 + g.parcalar.reduce((t, p) => t + agirlik(p), 0);\n&#' sinama/agirlik.mjs
sed -i.y 's#^  if (g.tip === "konsolide") {#  if (g.tip === "palet") return 3500 + g.parcalar.reduce((t, p) => t + ucret(p), 0);\n&#' sinama/ucret.mjs
sed -i.y 's#^  if (g.tip === "konsolide") return Math.max#  if (g.tip === "palet") return Math.max(...g.parcalar.map(sure)) + 2;\n&#' sinama/sure.mjs
sed -i.y 's#^  if (g.tip === "konsolide") return g.parcalar.reduce#  if (g.tip === "palet") return g.parcalar.reduce((t, p) => t + parcaSayisi(p), 0);\n&#' sinama/parca.mjs
sed -i.y -e 's#^import { Konsolide } from "./konsolide.mjs";#&\nimport { Palet } from "./palet.mjs";#' \
         -e 's#^const DUGUM = { konsolide: Konsolide };#const DUGUM = { konsolide: Konsolide, palet: Palet };#' bilesik/kur.mjs
rm -f *.y sinama/*.y bilesik/*.y
node kosum.mjs
node olc.mjs
for d in sinama bilesik; do
  echo "$d: duzenlenen dosya=$(diff -rq "../$d" "$d" | grep -c '^Files ')  yeni dosya=$(diff -rq "../$d" "$d" | grep -c '^Only in ')  eklenen satir=$(diff -rN "../$d" "$d" | grep '^>' | grep -cvE '^> *(//|$)')"
done
```

```
sinama : agirlik=65.60 kg  ucret=82072  sure=7 gun  parca=5
bilesik: agirlik=65.60 kg  ucret=82072  sure=7 gun  parca=5
ayrilan sonuc = 0 / 4
sinama   dosya=4  tur sinamasi=8  agac seklini bilen dosya=4  satir=25
bilesik  dosya=4  tur sinamasi=1  agac seklini bilen dosya=3  satir=43
sinama: duzenlenen dosya=4  yeni dosya=0  eklenen satir=4
bilesik: duzenlenen dosya=1  yeni dosya=1  eklenen satir=12
```

Sınama düzeninde dört var olan dosya düzenlendi ve tür sınaması 4'ten 8'e çıktı; bileşik
düzende bir dosya düzenlendi (düğüm kaydı) ve sınama sayısı 1'de kaldı. Düzenlenen dört gövde
daha önce doğru çalışan iki türün mantığını da taşıyordu; eklenen 12 satır ise yeni bir
dosyada duruyor. Sınama düzeninin satır maliyeti düşük (4'e 12) çünkü her dosyaya tek satır
girdi — fakat o tek satırların dördü de var olan bir koşul zincirinin içine girdi.

Bir ayrıntı sınama düzeninin gizli riskini gösteriyor: `parca.mjs`'nin palet dalı,
konsolide dalıyla birebir aynıdır. Bu dosyada sınamayı `g.tip !== "koli"` biçimine çevirmek
daha kısa görünür, ama üçüncü bir düğüm türü geldiğinde o dosya sessizce yanlış yanıt verir.
Bileşik düzende böyle bir kısayol yoktur, çünkü yanıtı veren şey türün kendisidir.

#### Yeni Bir İşlem Eklenince

İkinci genişleme yönü tersidir: yeni bir işlem, en ağır yaprağın kodu. Ağaç değişmediği için
temel sürüm yeniden kopyalanıyor.

```sh
mkdir -p yeni-islem && cp -r agac.mjs sinama bilesik olc.mjs yeni-islem/
ls yeni-islem/sinama
```

```
agirlik.mjs
parca.mjs
sure.mjs
ucret.mjs
```

```js
// yeni-islem/sinama/en-agir.mjs — besinci islem: en agir yaprak; tur sinamasi bir kez daha
export function enAgirParca(g) {
  if (g.tip === "konsolide") {
    return g.parcalar.map(enAgirParca).reduce((a, b) => (b[1] > a[1] ? b : a));
  }
  return [g.kod, g.agirlik];
}
```

```sh
cd yeni-islem
sed -i.y 's#^  parcaSayisi() { return 1; }#&\n  enAgirParca() { return [this.kod, this.kilo]; }#' bilesik/koli.mjs
sed -i.y 's#^  parcaSayisi() { return this.parcalar.reduce((t, p) => t + p.parcaSayisi(), 0); }#&\n  enAgirParca() {\n    return this.parcalar.map((p) => p.enAgirParca()).reduce((a, b) => (b[1] > a[1] ? b : a));\n  }#' bilesik/konsolide.mjs
rm -f bilesik/*.y
cat > kosum2.mjs <<'KOSUM'
// yeni-islem/kosum2.mjs — besinci islem iki duzende ayni yapragi buluyor mu
import { AGAC } from "./agac.mjs";
import { enAgirParca } from "./sinama/en-agir.mjs";
import { kur } from "./bilesik/kur.mjs";

console.log(`sinama : en agir = ${enAgirParca(AGAC).join(" ")}`);
console.log(`bilesik: en agir = ${kur(AGAC).enAgirParca().join(" ")}`);
KOSUM
node kosum2.mjs
for d in sinama bilesik; do
  echo "$d: duzenlenen dosya=$(diff -rq "../$d" "$d" | grep -c '^Files ')  yeni dosya=$(diff -rq "../$d" "$d" | grep -c '^Only in ')  eklenen satir=$(diff -rN "../$d" "$d" | grep '^>' | grep -cvE '^> *(//|$)')"
done
```

```
sinama : en agir = G-4 18
bilesik: en agir = G-4 18
sinama: duzenlenen dosya=0  yeni dosya=1  eklenen satir=6
bilesik: duzenlenen dosya=2  yeni dosya=0  eklenen satir=4
```

Yön tersine döndü: yeni işlem sınama düzeninde hiçbir var olan dosyaya dokunmadı, bileşik
düzende iki sınıfın ikisi de düzenlendi. Üç düğüm türü olsa üç sınıf düzenlenecekti. Bu,
Programlama Paradigmaları kursundaki Paradigma Karışımı dersinde tanımlanan **ifade
problemidir**: bileşik yeni tür eklemeyi ucuzlatır (4 düzenlenen dosya → 1), yeni işlem
eklemeyi pahalılaştırır (0 düzenlenen dosya → 2). Kalıp seçimi, hangi eksenin daha sık
büyüdüğü ölçüsüne dayanır ve o ölçünün kaynağı değişiklik günlüğüdür.

#### Bedel ve Ne Zaman Uygulanmayacağı

Bileşiğin üç bedeli vardır. Birincisi peşin satır maliyetidir: 21 satıra karşı 32 satır ve
iki yeni tip. İkincisi, işlem ekseninde ödenen bedeldir — her yeni işlem bütün düğüm
türlerinin gövdesine dokunur. Üçüncüsü arayüzün kapsamıdır. Buradaki ortak arayüz dört
sorgudan oluşuyor; içine `ekle` gibi bir düğüm işlemi konmadı. Konsaydı yaprak onu ya sessizce
yok sayacak ya hata atacaktı: **şeffaflık** (yaprakla düğüm birbirinden ayırt edilemez)
ile **güvenlik** (yaprakta anlamsız yöntem bulunmaz) arasında seçim yapılır ve burada güvenlik
seçildi. Bunun sonucu, ağacı değiştiren kodun düğüm türünü bilmek zorunda kalmasıdır.

Kalıp iki durumda karşılığını vermez. Derinlik **sabit** ve birse — gönderi listesi hep tek
düzeyse — özyineleme yoktur, tür sınaması da yoktur; bileşik yalnız iki sınıf ekler. İşlem
sayısı düğüm türü sayısından çok daha hızlı büyüyorsa ifade probleminin pahalı tarafına
düşülür; o durumda ağacı tek biçim gezen bir gezinti yordamı ile hesapları ayrı tutmak daha
az dosya düzenletir.

#### Özet

- Bileşiğin çözdüğü problem, ağaç üzerindeki her hesabın "yaprak mı, düğüm mü" sınamasını
  kendi gövdesinde tekrarlamasıdır; çözüm yaprakla düğümü aynı arayüzün arkasına koymaktır.
- Dört hesap için tür sınaması 4'ten 1'e, ağacın iç şeklini bilen dosya sayısı 4'ten 2'ye
  indi; bedel 11 satır ve iki yeni tiptir.
- Yeni düğüm türü (palet) sınama düzeninde 4 var olan dosyayı düzenletti ve sınama sayısını
  8'e çıkardı; bileşik düzende 1 dosya düzenlendi, 1 yeni dosya eklendi, sınama sayısı 1'de
  kaldı ve dört hesabın sonucu iki düzende de aynı çıktı.
- Yeni işlem (en ağır yaprak) tersini gösterdi: sınama düzeninde 0, bileşik düzende 2 var olan
  dosya düzenlendi — ifade probleminin iki yönü.
- Ortak arayüze ağacı değiştiren yöntemler konmadığı için yaprakta anlamsız yöntem yok;
  şeffaflık yerine güvenlik seçildi. Derinlik sabitse ya da işlem eksenindeki büyüme baskınsa
  kalıp karşılığını vermez.

#### Sonraki Adım

Bileşikte düğüm, parçalarına kendisiyle aynı arayüzle sorar; sarılan şey bir **küme**dir ve
sarma işlemi ağacı kurar. Bir sonraki soru aynı sarma tekniğini tek bir nesne üzerinde ve
başka bir amaçla kullanır: gönderinin ücretine sırayla yakıt farkı, sigorta, sözleşmeli
müşteri indirimi ve vergi eklenmesi gerekiyor. Bu dört ek her gönderide istenmez, birbirinden
bağımsız açılıp kapanır ve sırası sonucu değiştirir. Seçeneklerin her bileşimi bir alt sınıf
olursa sınıf sayısı seçenek sayısının ikinin kuvveti kadar büyür. Sonraki ders bu sayıyı dört
seçenek için ölçer, ücreti aynı arayüzle sarıp yeni davranışı katmanlayan dekoratör kalıbını
yazar ve bedelini izlenmesi gereken çağrı sayısıyla sıra duyarlılığıyla birlikte sayar.

### Dekoratör

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/dekorator

Davranışın katmanlanarak eklenmesi: birbirinden bağımsız açılıp kapanan dört ücret ekinin her bileşimi için bir alt sınıf üreten düzen ile aynı arayüzü koruyarak saran dekoratör zincirinin tip ve satır sayısıyla karşılaştırılması, beşinci ek geldiğinde büyümenin ölçülmesi, zincir derinliği ve sıra duyarlılığı cinsinden bedel.

Bileşikte sarılan şey bir kümeydi: düğüm, parçalarına kendisiyle aynı arayüzle soruyordu ve
sarma işlemi bir ağaç kuruyordu. Aynı teknik tek bir nesne üzerinde, başka bir amaçla da
kullanılır.

Gönderinin ücretine sırayla dört ek uygulanması gerekiyor: yakıt farkı, sigorta, sözleşmeli
müşteri indirimi ve vergi. Dördü birbirinden bağımsız açılıp kapanır — bir gönderide yalnız
vergi, başkasında yakıt farkı ile sigorta istenir. Her bileşim bir alt sınıf olursa sınıf
sayısı seçenek sayısının ikinin kuvveti kadar büyür. **Dekoratör** (decorator) bu bileşimi
sınıf yerine nesne düzeyinde kurar: her ek, sardığı nesnenin arayüzünü aynen döndüren ve
sonucun üstüne kendi katkısını koyan bir sarmalayıcıdır. Ölçüler tip sayısı, satır sayısı,
zincir derinliği ve sıra duyarlılığıdır.

#### Problem: Bağımsız Seçeneklerin Çarpımı

Taban ücret ve örnek gönderiler iki düzende de ortaktır.

```js
// tarife.mjs — iki duzenin de kullandigi taban ucret ve ornek gonderiler; tutar kurustur
const KADEME = [[1, 4990], [5, 8490], [15, 14990], [30, 24990]];
const KATSAYI = { 34: 100, "06": 115, 35: 120, 65: 145 };

export const kademeUcret = (agirlik, postaKodu) => {
  const k = KADEME.find(([ust]) => agirlik <= ust) ?? [0, 24990];
  return Math.round((k[1] * (KATSAYI[postaKodu.slice(0, 2)] ?? 165)) / 100);
};

export const GONDERILER = [
  { kod: "G-1", agirlik: 0.8, postaKodu: "34100", deger: 120000 },
  { kod: "G-2", agirlik: 12, postaKodu: "06500", deger: 40000 },
  { kod: "G-3", agirlik: 26, postaKodu: "65100", deger: 900000 },
];
```

Alt sınıf düzeninde her seçenek alt kümesi için bir sınıf gerekir: yalnız yakıt farkını
uygulayan, yakıt farkı ile vergiyi uygulayan, üçünü uygulayan… On altı sınıfın on altısını
elle yazmak bu dersin metnine sığmaz; onları bir üretici yazıyor. Üreticinin varlığı zaten
ölçünün kendisidir — elle yazılacak gövde sayısı budur.

```js
// uret.mjs — alt sinif patlamasini uretir: dort secenegin butun alt kumeleri
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";

export const ADIM = [
  ["Y", "t = Math.round(t * 1.08);"],
  ["S", "t = t + Math.max(500, Math.round(g.deger * 0.005));"],
  ["I", "t = Math.round(t * 0.88);"],
  ["V", "t = Math.round(t * 1.2);"],
];

export function uretVeYaz(yol, adimlar) {
  const parca = ['import { kademeUcret } from "../tarife.mjs";', ""];
  const adlar = [];
  for (let maske = 0; maske < 2 ** adimlar.length; maske += 1) {
    const secili = adimlar.filter((_, i) => (maske >> i) & 1);
    const ad = `Ucret${secili.map(([h]) => h).join("") || "0"}`;
    adlar.push(ad);
    parca.push(`export class ${ad} {`, "  hesapla(g) {",
      "    let t = kademeUcret(g.agirlik, g.postaKodu);",
      ...secili.map(([, kod]) => `    ${kod}`), "    return t;", "  }", "}");
  }
  parca.push(`export const SINIF = { ${adlar.join(", ")} };`);
  mkdirSync(dirname(yol), { recursive: true });
  writeFileSync(yol, `${parca.join("\n")}\n`);
  return adlar.length;
}

console.log(`uretilen sinif = ${uretVeYaz("altsinif/hesaplar.mjs", ADIM)}`);
```

```
uretilen sinif = 16
```

Üretilen dosyanın başı, gövdelerin nasıl tekrarlandığını gösteriyor.

```sh
head -16 altsinif/hesaplar.mjs
```

```
import { kademeUcret } from "../tarife.mjs";

export class Ucret0 {
  hesapla(g) {
    let t = kademeUcret(g.agirlik, g.postaKodu);
    return t;
  }
}
export class UcretY {
  hesapla(g) {
    let t = kademeUcret(g.agirlik, g.postaKodu);
    t = Math.round(t * 1.08);
    return t;
  }
}
export class UcretS {
```

Yakıt farkı satırı, onu içeren sekiz sınıfın sekizinde ayrı ayrı yazılıdır. Oran her seçenek
için aynıdır: bir seçeneğin kodu, alt kümelerin yarısında tekrarlanır.

#### Çözüm: Aynı Arayüzü Döndüren Sarmalayıcı

Dekoratörün çözümü, eki bir sınıf değil bir **nesne** yapmaktır. Sarmalayıcı içine aldığı
nesneyle aynı arayüzü sunar, bu yüzden kendisi de sarılabilir.

```js
// dekorator/temel.mjs — sarilacak cekirdek: yalniz taban ucreti bilir
import { kademeUcret } from "../tarife.mjs";

export const temelUcret = { hesapla: (g) => kademeUcret(g.agirlik, g.postaKodu) };
```

```js
// dekorator/sarmalar.mjs — dort dekorator: her biri ayni arayuzu doner, icine sardigini bilmez
export const yakitFarki = (ic) => ({ ic, hesapla: (g) => Math.round(ic.hesapla(g) * 1.08) });

export const sigorta = (ic) => ({
  ic, hesapla: (g) => ic.hesapla(g) + Math.max(500, Math.round(g.deger * 0.005)),
});

export const sozlesmeIndirimi = (ic) => ({ ic, hesapla: (g) => Math.round(ic.hesapla(g) * 0.88) });

export const vergi = (ic) => ({ ic, hesapla: (g) => Math.round(ic.hesapla(g) * 1.2) });

export const zincir = (temel, sarmalar) => sarmalar.reduce((n, f) => f(n), temel);

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

Her sarmalayıcı sardığı nesneye `ic` adıyla bir başvuru tutar; `derinlik` bu başvuruyu izleyerek
zincirin kaç gövdeden oluştuğunu söyler. Aşağıdaki koşum on altı alt kümenin her birini iki
düzende hesaplar ve karşılaştırır.

```js
// kosum.mjs — butun secenek alt kumelerinde iki duzen ayni tutari veriyor mu
import { GONDERILER } from "./tarife.mjs";
import { SINIF } from "./altsinif/hesaplar.mjs";
import { temelUcret } from "./dekorator/temel.mjs";
import { yakitFarki, sigorta, sozlesmeIndirimi, vergi, zincir, derinlik } from "./dekorator/sarmalar.mjs";

const SARMA = [["Y", yakitFarki], ["S", sigorta], ["I", sozlesmeIndirimi], ["V", vergi]];
const N = 2 ** SARMA.length;
let ayrilan = 0;
for (let maske = 0; maske < N; maske += 1) {
  const secili = SARMA.filter((_, i) => (maske >> i) & 1);
  const ad = `Ucret${secili.map(([h]) => h).join("") || "0"}`;
  const alt = new SINIF[ad]();
  const sarilmis = zincir(temelUcret, secili.map(([, f]) => f));
  for (const g of GONDERILER) if (alt.hesapla(g) !== sarilmis.hesapla(g)) ayrilan += 1;
  if ([0, 1, 5, N - 1].includes(maske)) {
    console.log(`${ad.padEnd(10)} G-1=${alt.hesapla(GONDERILER[0])}  G-3=${alt.hesapla(GONDERILER[2])}  ` +
      `zincir derinligi=${derinlik(sarilmis)}`);
  }
}
console.log(`secenek=${SARMA.length}  alt kume=${N}  ayrilan tutar = ${ayrilan} / ${N * GONDERILER.length}`);
```

```
Ucret0     G-1=4990  G-3=36236  zincir derinligi=1
UcretY     G-1=5389  G-3=39135  zincir derinligi=2
UcretYI    G-1=4742  G-3=34439  zincir derinligi=3
UcretYSIV  G-1=6324  G-3=46079  zincir derinligi=5
secenek=4  alt kume=16  ayrilan tutar = 0 / 48
```

Kırk sekiz hesabın kırk sekizi aynı — on altı alt küme, üç gönderi. Fark tutarlarda değil,
kaç tipin taşındığında.

```js
// olc.mjs — iki duzendeki tip ve satir sayisini sayar
import { readdirSync, readFileSync } from "node:fs";

for (const dizin of ["altsinif", "dekorator"]) {
  let tip = 0, satir = 0;
  for (const d of readdirSync(dizin).filter((x) => x.endsWith(".mjs")).sort()) {
    const metin = readFileSync(`${dizin}/${d}`, "utf8");
    tip += (metin.match(/^export class \w+/gm) ?? []).length
      + (metin.match(/^export const \w+ = \(ic\)/gm) ?? []).length
      + (metin.match(/^export const temelUcret/gm) ?? []).length;
    satir += metin.split("\n").filter((s) => s.trim() !== "" && !s.trim().startsWith("//")).length;
  }
  console.log(`${dizin.padEnd(10)} tip=${String(tip).padStart(2)}  satir=${String(satir).padStart(3)}`);
}
```

```
altsinif   tip=16  satir=130
dekorator  tip= 5  satir= 10
```

On altı tipe ve 130 satıra karşı beş tip ve 10 satır. Beş tip, çekirdek ile dört sarmalayıcıdır;
seçeneklerin bileşimi hiçbir yerde bir tip olarak yazılmaz, çalışma zamanında kurulur.

#### Beşinci Ek Geldiğinde

Yeni gereksinim uzak bölge ekidir: sabit 1500 kuruş. Alt sınıf düzeninde üretici tablosuna bir
satır, dekoratör düzeninde sarmalayıcı dosyasına bir satır girer.

```sh
mkdir -p yeni && cp -r tarife.mjs uret.mjs dekorator kosum.mjs olc.mjs yeni/
cd yeni
sed -i.y 's#^\];#  ["U", "t = t + 1500;"],\n];#' uret.mjs
cat >> dekorator/sarmalar.mjs <<'SARMA'

export const uzakBolgeEki = (ic) => ({ ic, hesapla: (g) => ic.hesapla(g) + 1500 });
SARMA
sed -i.y -e 's#, vergi, zincir, derinlik }#, vergi, uzakBolgeEki, zincir, derinlik }#' \
         -e 's#\["V", vergi\]\];#["V", vergi], ["U", uzakBolgeEki]];#' kosum.mjs
rm -f *.y
node uret.mjs
node kosum.mjs | tail -2
node olc.mjs
echo "dekorator: eklenen satir=$(diff -rN ../dekorator dekorator | grep '^>' | grep -cvE '^> *(//|$)')  duzenlenen dosya=$(diff -rq ../dekorator dekorator | grep -c '^Files ')"
```

```
uretilen sinif = 32
UcretYSIVU G-1=7824  G-3=47579  zincir derinligi=6
secenek=5  alt kume=32  ayrilan tutar = 0 / 96
altsinif   tip=32  satir=274
dekorator  tip= 6  satir= 11
dekorator: eklenen satir=1  duzenlenen dosya=1
```

İki ölçüm noktası büyüme kuralını veriyor: alt sınıf düzeninde tip sayısı 16'dan 32'ye,
satır sayısı 130'dan 274'e çıktı; dekoratör düzeninde 5'ten 6'ya ve 10'dan 11'e. Alt sınıf
sayısı seçenek başına ikiye katlanır, dekoratör sayısı seçenek başına bir artar. Doksan altı
hesabın doksan altısı iki düzende de aynı kaldı.

#### Bedel: Zincir Derinliği ve Sıra

Dekoratörün ilk bedeli koşum çıktısında zaten görünüyor: zincir derinliği 1'den 5'e, beşinci
ekle 6'ya çıktı. Alt sınıf düzeninde bir tutar tek gövdede hesaplanır ve hata ayıklarken tek
gövde okunur; dekoratör düzeninde aynı tutar için altı gövde sırayla izlenir. Çekirdeğe
ulaşmak isteyen bir kod da `ic` başvurusunu izlemek zorundadır; bu, Tasarım İlkeleri kursundaki
Demeter Yasası dersinin ölçtüğü erişim zincirini uzatır.

İkinci bedel daha sinsidir: sonuç sarma **sırasına** duyarlıdır.

```js
// sira.mjs — ayni iki dekorator iki sirada: sigorta once mi, indirim once mi
import { GONDERILER } from "./tarife.mjs";
import { temelUcret } from "./dekorator/temel.mjs";
import { sigorta, sozlesmeIndirimi, zincir } from "./dekorator/sarmalar.mjs";

const g = GONDERILER[0];
const a = zincir(temelUcret, [sigorta, sozlesmeIndirimi]).hesapla(g);
const b = zincir(temelUcret, [sozlesmeIndirimi, sigorta]).hesapla(g);
console.log(`sigorta -> indirim = ${a}`);
console.log(`indirim -> sigorta = ${b}`);
console.log(`fark = ${b - a} kurus`);
```

```
sigorta -> indirim = 4919
indirim -> sigorta = 4991
fark = 72 kurus
```

Yetmiş iki kuruş, indirimin sigorta bedeline uygulanıp uygulanmamasından geliyor: çarpan ek ile
toplanan ek yer değiştirdiğinde sonuç değişir. Alt sınıf düzeninde sıra sınıfın gövdesinde
sabittir ve okunabilir; dekoratör düzeninde sıra zincirin kurulduğu yerdedir ve orada yazılmamış
bir kural olarak kalır. Kalıbın kullanıldığı her yerde sıranın nerede tanımlandığı açıkça
gösterilmelidir.

Üçüncü bedel geçersiz bileşimlerin engellenememesidir. Zincir kurma işlemi her alt kümeyi kabul
eder; "sözleşmeli müşteri indirimi ile hacim indirimi aynı gönderide olmaz" gibi bir kural
hiçbir sarmalayıcının göremediği bir kuraldır, çünkü her sarmalayıcı yalnız içine aldığını bilir.

Buradan iki uygulanmama koşulu çıkar. Seçenek sayısı bir ya da ikiyse ikinin kuvveti küçüktür
(iki ya da dört) ve dekoratörün dolaylılığı karşılığını vermez. Ekler birbirine bağımlıysa —
biri diğerinin varlığını gerektiriyor ya da dışlıyorsa — bağımsız katmanlama varsayımı düşer;
o durumda kuralı tek gövdede toplayan bir hesap daha az yanlış bileşim üretir.

Kalıbın bağdaştırıcı ve köprüden ayrımı da buradadır: bağdaştırıcı arayüzü **değiştirir**, köprü
iki ekseni **ayırır**, dekoratör arayüzü **korur** ve davranışı üstüne ekler. Sarmalayıcının
aynı arayüzü döndürmesi, zincirin sınırsız uzayabilmesinin tek nedenidir.

#### Özet

- Dekoratörün çözdüğü problem, birbirinden bağımsız açılıp kapanan eklerin her bileşimi için
  bir tip gerekmesidir; çözüm eki nesne yapmak ve sarılanın arayüzünü aynen döndürmektir.
- Dört seçenek için alt sınıf düzeni 16 tip ve 130 satır, dekoratör düzeni 5 tip ve 10 satır
  taşıdı; on altı alt kümenin kırk sekiz hesabı iki düzende de aynı çıktı.
- Beşinci ek alt sınıf düzenini 32 tipe ve 274 satıra çıkardı, dekoratör düzenini 6 tipe ve
  11 satıra: alt sınıf sayısı seçenek başına ikiye katlanır, dekoratör sayısı bir artar.
- Bedel ölçülür: zincir derinliği 1'den 6'ya çıktı, yani bir tutar için izlenecek gövde sayısı
  altıya çıktı; sıra değişimi aynı iki ekte 72 kuruş fark üretti.
- Seçenek sayısı bir ya da ikiyse ya da ekler birbirine bağımlıysa kalıp karşılığını vermez;
  zincir kurma işlemi geçersiz bileşimleri engelleyemez.

#### Sonraki Adım

Dekoratör zincirini kuran kod, hangi eklerin hangi sırayla uygulanacağını bilmek zorundaydı.
Bu bilgi tek bir çağrı için katlanılır, ama kitaplığın tamamı böyle çalışırsa istemci sıralı
adımların hepsini öğrenmek zorunda kalır: adresi normalleştir, bölgeyi çöz, tarifeyi seç,
ekleri uygula, taşıyıcıyı seç, teslim tarihini hesapla. Altı modülün doğru sırayla çağrılması
bilgisi her istemcide tekrar eder; bir adım atlanırsa hata çalışma zamanında çıkar. Sonraki
ders istemcinin doğrudan bağ sayısını ve sıralı adım bilgisinin kaç yerde tekrarlandığını
sayar, alt sistemin önüne tek bir giriş noktası koyan cephe kalıbını yazar ve cephenin ithal
kapanışını küçültüp küçültmediğini ölçer.

### Cephe

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/cephe

Karmaşık alt sistemin sadeleştirilmesi: altı adımlı gönderi kabul akışını istemcilerin kendisinin sıraladığı düzen ile tek giriş noktası arkasına alan düzenin doğrudan bağ, ithal kapanışı ve bilinen adım sayısıyla karşılaştırılması, bir adım atlandığında ortaya çıkan farkın ölçülmesi ve akışa yeni adım eklendiğinde düzenlenen dosyaların sayılması.

Dekoratör zincirini kuran kod, hangi eklerin hangi sırayla uygulanacağını bilmek zorundaydı.
Tek bir çağrı için bu katlanılır bir bilgidir. Kitaplığın tamamı böyle çalıştığında ise
istemci sıralı adımların hepsini öğrenmek zorunda kalır.

Gönderi kabulü altı adımdır: adresi düzelt, bölgeyi çöz, taban ücreti hesapla, ekleri uygula,
taşıyıcıyı seç, teslim tarihini bul. Sıra keyfi değildir — bölge düzeltilmiş posta kodundan
çıkar, ücret bölgeye bağlıdır, taşıyıcı kapsamı yine bölgeye. İki istemci bu altı adımı ayrı
ayrı biliyorsa aynı sıra iki yerde durur ve bir adım atlandığında hata çalışma zamanında,
yanlış bir tutar biçiminde çıkar. **Cephe** (facade) alt sistemin önüne tek bir giriş noktası
koyar. Ölçüler: istemcinin doğrudan bağ sayısı, ithal kapanışı boyutu ve sıralı adım
bilgisinin kaç dosyada bulunduğu.

#### Problem: Sıra Bilgisi İstemcide

Alt sistem altı küçük modüldür; her biri tek bir işi yapar ve ötekileri tanımaz.

```js
// alt/adres.mjs — alt sistem 1: adresi normallestirir
export const adresiDuzelt = (adres) => ({
  postaKodu: String(adres.postaKodu ?? "").replace(/\s+/g, ""),
  il: String(adres.il ?? "").trim(),
});
```

```js
// alt/bolge.mjs — alt sistem 2: posta kodundan ucret bolgesi
const POSTA_BOLGE = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" };

export const bolgeBul = (postaKodu) => POSTA_BOLGE[postaKodu.slice(0, 2)] ?? "bilinmeyen";
```

```js
// alt/tarife.mjs — alt sistem 3: agirlik kademesi ve bolge katsayisi
const KADEME = [[1, 4990], [5, 8490], [15, 14990], [30, 24990]];
const KATSAYI = { yakin: 100, orta: 118, uzak: 145, bilinmeyen: 165 };

export const tabanUcret = (agirlik, bolge) => {
  const k = KADEME.find(([ust]) => agirlik <= ust) ?? [0, 24990];
  return Math.round((k[1] * KATSAYI[bolge]) / 100);
};
```

```js
// alt/ekler.mjs — alt sistem 4: sigorta ve vergi ekleri
export const ekleriUygula = (tutar, secenekler, deger) => {
  const sigortali = secenekler.sigorta === true ? Math.max(500, Math.round(deger * 0.005)) : 0;
  return Math.round((tutar + sigortali) * (secenekler.vergi === true ? 1.2 : 1));
};
```

```js
// alt/tasiyici.mjs — alt sistem 5: bolgeye gore en ucuz tasiyici
const CARPAN = { kara: 1, hava: 1.35, deniz: 0.9 };
const KAPSAM = { yakin: ["kara", "hava", "deniz"], orta: ["kara", "hava"], uzak: ["kara"], bilinmeyen: ["kara"] };

export const enUcuzTasiyici = (tutar, bolge) => KAPSAM[bolge]
  .map((t) => ({ tasiyici: t, tutar: Math.round(tutar * CARPAN[t]) }))
  .sort((a, b) => a.tutar - b.tutar)[0];
```

```js
// alt/teslim.mjs — alt sistem 6: bolgeye gore teslim tarihi
const GUN = { yakin: 1, orta: 2, uzak: 4, bilinmeyen: 5 };

export const teslimTarihi = (baslangic, bolge) => {
  const t = new Date(`${baslangic}T00:00:00Z`);
  t.setUTCDate(t.getUTCDate() + GUN[bolge]);
  return t.toISOString().slice(0, 10);
};
```

```js
// istekler.mjs — iki duzenin de isledigi istekler
export const ISTEKLER = [
  { kod: "G-1", agirlik: 0.8, deger: 120000, adres: { postaKodu: " 34100 ", il: " Istanbul " },
    secenekler: { sigorta: true, vergi: true }, tarih: "2026-03-02" },
  { kod: "G-2", agirlik: 12, deger: 40000, adres: { postaKodu: "06500", il: "Ankara" },
    secenekler: { vergi: true }, tarih: "2026-03-02" },
  { kod: "G-3", agirlik: 26, deger: 900000, adres: { postaKodu: "65100", il: "Van" },
    secenekler: {}, tarih: "2026-03-30" },
];
```

İlk düzende iki istemci altı modülü kendisi sıralar. İkinci istemci farklı bir sonuç
döndürüyor ama aynı altı adımı baştan tekrarlıyor.

```js
// dogrudan/kabul.mjs — istemci alti modulu kendisi sirayla cagirir
import { adresiDuzelt } from "../alt/adres.mjs";
import { bolgeBul } from "../alt/bolge.mjs";
import { tabanUcret } from "../alt/tarife.mjs";
import { ekleriUygula } from "../alt/ekler.mjs";
import { enUcuzTasiyici } from "../alt/tasiyici.mjs";
import { teslimTarihi } from "../alt/teslim.mjs";

export function kabulEt(istek) {
  const adres = adresiDuzelt(istek.adres);
  const bolge = bolgeBul(adres.postaKodu);
  const taban = tabanUcret(istek.agirlik, bolge);
  const tutar = ekleriUygula(taban, istek.secenekler, istek.deger);
  const secim = enUcuzTasiyici(tutar, bolge);
  return { kod: istek.kod, bolge, ...secim, tarih: teslimTarihi(istek.tarih, bolge) };
}
```

```js
// dogrudan/teklif.mjs — ikinci istemci ayni sirayi bastan tekrar eder
import { adresiDuzelt } from "../alt/adres.mjs";
import { bolgeBul } from "../alt/bolge.mjs";
import { tabanUcret } from "../alt/tarife.mjs";
import { ekleriUygula } from "../alt/ekler.mjs";
import { enUcuzTasiyici } from "../alt/tasiyici.mjs";
import { teslimTarihi } from "../alt/teslim.mjs";

export function teklifVer(istek) {
  const adres = adresiDuzelt(istek.adres);
  const bolge = bolgeBul(adres.postaKodu);
  const taban = tabanUcret(istek.agirlik, bolge);
  const tutar = ekleriUygula(taban, istek.secenekler, istek.deger);
  const secim = enUcuzTasiyici(tutar, bolge);
  return { tutar: secim.tutar, tarih: teslimTarihi(istek.tarih, bolge) };
}
```

#### Çözüm: Alt Sistemin Önünde Tek Giriş

Cephe alt sistemi gizlemez, ona giden **tek bir yol** açar. İçinde iş kuralı yoktur; yalnız
sıra ve iki dış işlem vardır.

```js
// cephe/kabul-cephesi.mjs — cephe: alt sistemin sirasini bilen tek yer, dis yuzu iki islem
import { adresiDuzelt } from "../alt/adres.mjs";
import { bolgeBul } from "../alt/bolge.mjs";
import { tabanUcret } from "../alt/tarife.mjs";
import { ekleriUygula } from "../alt/ekler.mjs";
import { enUcuzTasiyici } from "../alt/tasiyici.mjs";
import { teslimTarihi } from "../alt/teslim.mjs";

const hesapla = (istek) => {
  const adres = adresiDuzelt(istek.adres);
  const bolge = bolgeBul(adres.postaKodu);
  const taban = tabanUcret(istek.agirlik, bolge);
  const tutar = ekleriUygula(taban, istek.secenekler, istek.deger);
  return { bolge, secim: enUcuzTasiyici(tutar, bolge), tarih: teslimTarihi(istek.tarih, bolge) };
};

export function kabulEt(istek) {
  const s = hesapla(istek);
  return { kod: istek.kod, bolge: s.bolge, ...s.secim, tarih: s.tarih };
}

export function teklifVer(istek) {
  const s = hesapla(istek);
  return { tutar: s.secim.tutar, tarih: s.tarih };
}
```

```js
// cephe/kabul.mjs — ayni istemci, tek bag
import { kabulEt as cepheKabul } from "./kabul-cephesi.mjs";

export const kabulEt = (istek) => cepheKabul(istek);
```

```js
// cephe/teklif.mjs — ikinci istemci, tek bag
import { teklifVer as cepheTeklif } from "./kabul-cephesi.mjs";

export const teklifVer = (istek) => cepheTeklif(istek);
```

```js
// kosum.mjs — iki duzen ayni kabul kaydini ve ayni teklifi uretiyor mu
import { ISTEKLER } from "./istekler.mjs";
import { kabulEt as dogrudanKabul } from "./dogrudan/kabul.mjs";
import { teklifVer as dogrudanTeklif } from "./dogrudan/teklif.mjs";
import { kabulEt as cepheKabul } from "./cephe/kabul.mjs";
import { teklifVer as cepheTeklif } from "./cephe/teklif.mjs";

let ayrilan = 0;
for (const istek of ISTEKLER) {
  const a = JSON.stringify([dogrudanKabul(istek), dogrudanTeklif(istek)]);
  const b = JSON.stringify([cepheKabul(istek), cepheTeklif(istek)]);
  if (a !== b) ayrilan += 1;
  console.log(`${istek.kod} ${JSON.stringify(dogrudanKabul(istek))}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${ISTEKLER.length}`);
```

```
G-1 {"kod":"G-1","bolge":"yakin","tasiyici":"deniz","tutar":6037,"tarih":"2026-03-03"}
G-2 {"kod":"G-2","bolge":"orta","tasiyici":"kara","tutar":21226,"tarih":"2026-03-04"}
G-3 {"kod":"G-3","bolge":"uzak","tasiyici":"kara","tutar":36236,"tarih":"2026-04-03"}
ayrilan sonuc = 0 / 3
```

#### Bağın ve Kapanışın Ölçülmesi

Ölçüm Tasarım İlkeleri kursundaki iki ayrı ölçüyü birlikte kullanıyor: doğrudan bağ sayısı
(bir dosyanın kendi ithal satırları) ve ithal kapanışı boyutu (o dosyanın dolaylı olarak
bağlandığı bütün dosyalar).

```js
// grafik.mjs — istemcilerin dogrudan bag sayisi, ithal kapanisi ve adim sirasini bilen dosyalar
import { readdirSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";

const ADIM = ["adresiDuzelt", "bolgeBul", "tabanUcret", "ekleriUygula", "enUcuzTasiyici", "teslimTarihi"];

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

const kapanis = (yol, gorulen = new Set()) => {
  for (const c of ithaller(yol)) if (!gorulen.has(c)) { gorulen.add(c); kapanis(c, gorulen); }
  return gorulen;
};

for (const yol of ["dogrudan/kabul.mjs", "dogrudan/teklif.mjs", "cephe/kabul.mjs", "cephe/teklif.mjs"]) {
  const metin = readFileSync(yol, "utf8");
  const adim = ADIM.filter((a) => metin.includes(a)).length;
  console.log(`${yol.padEnd(20)} dogrudan bag=${ithaller(yol).length}  ithal kapanisi=${kapanis(yol).size}  ` +
    `bildigi adim=${adim}/6`);
}

const yuzey = (dosyalar) => dosyalar
  .reduce((t, y) => t + (readFileSync(y, "utf8").match(/^export /gm) ?? []).length, 0);

console.log(`alt sistem yuzeyi = ${yuzey(readdirSync("alt").map((d) => `alt/${d}`))} islev, ` +
  `cephe yuzeyi = ${yuzey(["cephe/kabul-cephesi.mjs"])} islem`);
```

```
dogrudan/kabul.mjs   dogrudan bag=6  ithal kapanisi=6  bildigi adim=6/6
dogrudan/teklif.mjs  dogrudan bag=6  ithal kapanisi=6  bildigi adim=6/6
cephe/kabul.mjs      dogrudan bag=1  ithal kapanisi=7  bildigi adim=0/6
cephe/teklif.mjs     dogrudan bag=1  ithal kapanisi=7  bildigi adim=0/6
alt sistem yuzeyi = 6 islev, cephe yuzeyi = 2 islem
```

Üç sayı üç ayrı şey söylüyor. Doğrudan bağ 6'dan 1'e indi. Bildiği adım 6/6'dan 0/6'ya indi:
sıra bilgisi iki istemciden çıkıp tek dosyada toplandı. Buna karşılık **ithal kapanışı
küçülmedi, 6'dan 7'ye çıktı** — cephe bir dosya olarak kapanışa eklendi. Cephe derleme
bağımlılığını azaltmaz; azalttığı şey istemcinin bilmek zorunda olduğu ad ve sıra sayısıdır.
Kapanışın gerçekten küçülmesi için alt sistemin bir kısmının hiç kullanılmaması gerekirdi.

Son satır cephenin yüzeyini veriyor: alt sistem 6 işlev sunuyor, cephe 2 işlem. Bu daralma
kazanç olduğu kadar bedeldir; aşağıda ölçülüyor.

#### Bir Adım Atlanınca

Sıra bilgisinin istemcide durmasının maliyeti, atlanan bir adımın hata vermeden yanlış sonuç
üretmesidir.

```js
// atlanan.mjs — dogrudan duzende ilk adim atlanirsa ne olur
import { ISTEKLER } from "./istekler.mjs";
import { kabulEt } from "./dogrudan/kabul.mjs";
import { bolgeBul } from "./alt/bolge.mjs";
import { tabanUcret } from "./alt/tarife.mjs";
import { ekleriUygula } from "./alt/ekler.mjs";
import { enUcuzTasiyici } from "./alt/tasiyici.mjs";
import { teslimTarihi } from "./alt/teslim.mjs";

// Ayni sira, tek eksikle: adresiDuzelt cagrilmadi.
function atlayarakKabul(istek) {
  const bolge = bolgeBul(istek.adres.postaKodu);
  const taban = tabanUcret(istek.agirlik, bolge);
  const tutar = ekleriUygula(taban, istek.secenekler, istek.deger);
  const secim = enUcuzTasiyici(tutar, bolge);
  return { kod: istek.kod, bolge, ...secim, tarih: teslimTarihi(istek.tarih, bolge) };
}

const g = ISTEKLER[0];
const dogru = kabulEt(g), eksik = atlayarakKabul(g);
console.log(`tam sira   : ${JSON.stringify(dogru)}`);
console.log(`adim atlandi: ${JSON.stringify(eksik)}`);
const gun = (Date.parse(eksik.tarih) - Date.parse(dogru.tarih)) / 86400000;
console.log(`tutar farki = ${eksik.tutar - dogru.tutar} kurus, teslim farki = ${gun} gun`);
```

```
tam sira   : {"kod":"G-1","bolge":"yakin","tasiyici":"deniz","tutar":6037,"tarih":"2026-03-03"}
adim atlandi: {"kod":"G-1","bolge":"bilinmeyen","tasiyici":"kara","tutar":10601,"tarih":"2026-03-07"}
tutar farki = 4564 kurus, teslim farki = 4 gun
```

Posta kodundaki tek bir boşluk yüzünden bölge "bilinmeyen" oldu; ücret 4564 kuruş yüksek, teslim
tarihi 4 gün ileri çıktı ve hiçbir hata atılmadı. Cephe düzeninde bu hata istemcide yapılamaz,
çünkü istemcinin çağırdığı adım yoktur.

#### Akışa Yeni Adım Girince

Yeni gereksinim yasak madde denetimidir ve akışın ortasına, bölge çözümünden hemen sonra girer.

```sh
mkdir -p yeni && cp -r alt dogrudan cephe istekler.mjs kosum.mjs grafik.mjs yeni/
ls yeni/alt
```

```
adres.mjs
bolge.mjs
ekler.mjs
tarife.mjs
tasiyici.mjs
teslim.mjs
```

```js
// yeni/alt/denetim.mjs — alt sistem 7: yasak madde denetimi
const YASAK = new Set(["yanici", "asindirici"]);

export const yasakMi = (istek) => (istek.icerik ?? []).some((i) => YASAK.has(i));
```

```sh
cd yeni
sed -i.y -e 's#^import { tabanUcret } from "../alt/tarife.mjs";#&\nimport { yasakMi } from "../alt/denetim.mjs";#' \
         -e 's#^  const bolge = bolgeBul(adres.postaKodu);#&\n  if (yasakMi(istek)) return { kod: istek.kod, red: "yasak madde" };#' dogrudan/kabul.mjs
sed -i.y -e 's#^import { tabanUcret } from "../alt/tarife.mjs";#&\nimport { yasakMi } from "../alt/denetim.mjs";#' \
         -e 's#^  const bolge = bolgeBul(adres.postaKodu);#&\n  if (yasakMi(istek)) return { red: "yasak madde" };#' dogrudan/teklif.mjs
sed -i.y -e 's#^import { tabanUcret } from "../alt/tarife.mjs";#&\nimport { yasakMi } from "../alt/denetim.mjs";#' \
         -e 's#^  const bolge = bolgeBul(adres.postaKodu);#&\n  if (yasakMi(istek)) return { red: "yasak madde" };#' \
         -e 's#^  return { kod: istek.kod, bolge: s.bolge, ...s.secim, tarih: s.tarih };#  if (s.red !== undefined) return { kod: istek.kod, red: s.red };\n&#' \
         -e 's#^  return { tutar: s.secim.tutar, tarih: s.tarih };#  if (s.red !== undefined) return { red: s.red };\n&#' cephe/kabul-cephesi.mjs
sed -i.y 's#  { kod: "G-3", agirlik: 26#  { kod: "G-4", agirlik: 2, deger: 5000, adres: { postaKodu: "34100", il: "Istanbul" },\n    secenekler: {}, tarih: "2026-03-02", icerik: ["yanici"] },\n  { kod: "G-3", agirlik: 26#' istekler.mjs
rm -f dogrudan/*.y cephe/*.y *.y
node kosum.mjs | tail -2
node grafik.mjs | head -2
for d in dogrudan cephe; do
  echo "$d duzenlenen: $(diff -rq "../$d" "$d" | grep '^Files ' | sed 's#.*/\([a-z-]*\.mjs\).*#\1#' | tr '\n' ' ')"
done
```

```
G-3 {"kod":"G-3","bolge":"uzak","tasiyici":"kara","tutar":36236,"tarih":"2026-04-03"}
ayrilan sonuc = 0 / 4
dogrudan/kabul.mjs   dogrudan bag=7  ithal kapanisi=7  bildigi adim=6/6
dogrudan/teklif.mjs  dogrudan bag=7  ithal kapanisi=7  bildigi adim=6/6
dogrudan duzenlenen: kabul.mjs teklif.mjs
cephe duzenlenen: kabul-cephesi.mjs
```

Doğrudan düzende iki istemcinin ikisi de düzenlendi ve doğrudan bağ sayısı 6'dan 7'ye çıktı.
Cephe düzeninde yalnız cephe düzenlendi; iki istemci dosyası da olduğu gibi kaldı ve bağ
sayıları 1'de durdu. Dört isteğin dördünde iki düzen aynı yanıtı verdi — yasak madde taşıyan
G-4 için ikisi de reddetti. İstemci sayısı arttıkça fark doğrusal büyür: on istemci olsa
doğrudan düzende on dosya düzenlenecek, cephe düzeninde yine bir.

#### Bedel ve Ne Zaman Uygulanmayacağı

Cephenin bedeli üç maddedir. Birincisi bir dosya ve bir dolaylılık basamağıdır; ithal kapanışı
da bir büyüdü. İkincisi yüzeyin daralmasıdır: alt sistemin 6 işlevine karşı cephe 2 işlem
sunuyor. Cephenin geçirmediği bir yetenek — yalnız bölgeyi çözmek, taşıyıcıyı seçmeden ücret
hesaplamak — istemciye kapalıdır. Kalıp alt sistemi **kapatmaz**, alt sistem modülleri
kullanılabilir olarak kalır; ama istemci alt sisteme doğrudan gittiği anda cephenin sağladığı
sıra güvencesi de biter.

Üçüncüsü en sık rastlanan bozulmadır: cephe iş kuralı biriktirmeye başlarsa. Buradaki cephede
tek bir karar yok — yalnız çağrı sırası var. Cepheye indirim eşiği, red kuralı ya da tarife
seçimi yazıldığı anda dosya alt sistemin yedinci modülü olur ve hiçbir yerde ölçülmeyen bir
sorumluluk yığını büyür. Kalıbın sınırı şudur: cephe **sıra** bilir, **kural** bilmez.

İki durumda uygulanmaz. Alt sistem tek modül ve tek çağrıdan oluşuyorsa sıra bilgisi yoktur;
cephe yalnız yeniden adlandırma yapar. İstemcilerin her biri alt sistemin farklı bir alt
kümesini farklı bir sırayla kullanıyorsa tek bir cephe onların hiçbirine yetmez; o durumda
cepheye eklenen her seçenek bir bayrak parametresi olarak birikir ve Temiz Kod kursunda ölçülen
bayrak maliyeti geri döner.

#### Özet

- Cephenin çözdüğü problem, sıralı çağrı bilgisinin her istemcide tekrarlanmasıdır; çözüm alt
  sistemin önüne sırayı bilen tek bir giriş noktası koymaktır.
- Doğrudan bağ sayısı istemci başına 6'dan 1'e, bilinen adım sayısı 6/6'dan 0/6'ya indi; üç
  isteğin sonuçları iki düzende de aynı kaldı.
- İthal kapanışı 6'dan 7'ye **çıktı**: cephe derleme bağımlılığını azaltmaz, istemcinin bilmesi
  gereken ad ve sıra sayısını azaltır.
- Doğrudan düzende bir adım atlandığında hata atılmadı; ücret 4564 kuruş, teslim tarihi 4 gün
  saptı. Akışa yedinci adım girince doğrudan düzende iki istemci de düzenlendi, cephe düzeninde
  yalnız cephe düzenlendi.
- Bedel bir dosya, bir dolaylılık basamağı ve yüzeyin 6 işlevden 2 işleme daralmasıdır; cephe
  sıra bilir, kural bilmez — kural biriktiği anda alt sistemin bir modülüne dönüşür.

#### Sonraki Adım

Buraya kadarki dört kalıp nesneleri birleştirmenin farklı biçimlerini kurdu ve hepsinde ölçü
dosya, tip ya da bağ sayısıydı. Bir sonraki problem başka bir kaynağı ölçer: belleği. Ücret
hesabı sırasında her gönderi için bir tarife nesnesi kuruluyorsa, yüz binlerce gönderilik bir
toplu işlemde aynı kademe tablosu yüz binlerce kez kopyalanır. Oysa tablonun içeriği gönderiye
göre değişmez; gönderiye göre değişen şey yalnız ağırlık ve adrestir. Sonraki ders nesne
sayısını sayar, değişmeyen bilgiyi paylaşılan bir nesnede toplayan sinek siklet kalıbını yazar
ve kazancı hem nesne sayısıyla hem de ortama bağlı olduğu işaretlenmiş bir yığın ölçümüyle
gösterir.

### Sinek Siklet

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/sinek-siklet

Paylaşımla bellek tasarrufu: toplu ücretlendirmede her gönderinin kendi tarife kopyasını taşıdığı düzen ile değişmeyen tarife bilgisini paylaşılan nesnede toplayan düzenin nesne sayısı ve işaretlenmiş yığın ölçümüyle karşılaştırılması, paylaşımın değiştirilemezlik zorunluluğu ve paylaşım genişliği cinsinden bedeli.

Önceki beş kalıpta ölçü dosya, tip ve bağ sayısıydı; hepsi kaynak metnin büyüklüğüne
bakıyordu. Bu dersin ölçüsü çalışma zamanındadır: bellek.

Toplu ücretlendirme işi iki yüz bin gönderi satırını işliyor. Her gönderi bir tarife bilgisine
ihtiyaç duyar: ağırlık kademeleri tablosu, bölge katsayıları, para birimi. Bu bilginin içeriği
gönderiye göre **değişmez** — aynı tarife kodunu taşıyan bütün gönderilerde birebir aynıdır.
Gönderiye göre değişen şey ağırlık ve adrestir. **Sinek siklet** (flyweight) kalıbı bu ikisini
ayırır: değişmeyen kısım **iç durum** olarak tek bir nesnede paylaşılır, değişen kısım **dış
durum** olarak kaydın kendisinde kalır. Ölçüler kurulan nesne sayısı ve yığın kullanımıdır.

#### Problem: Aynı Tablonun İki Yüz Bin Kopyası

Ham satırlar ve tarife tabloları iki düzende de ortaktır. Satırlar dizinden türetildiği için
her koşumda aynıdır; rastgelelik yoktur.

```js
// veri.mjs — ham gonderi satirlari ve tarife tablolari; degerler dizinden turetilir
export const KADEME_TABLO = {
  standart: [[1, 4990], [5, 8490], [15, 14990], [30, 24990]],
  ekonomik: [[1, 3990], [5, 6990], [15, 11990], [30, 19990]],
  hizli: [[1, 6490], [5, 10990], [15, 18990], [30, 31990]],
};
export const KATSAYI_TABLO = { 34: 100, "06": 118, 35: 120, 65: 145 };
export const TARIFE_KODU = ["standart", "ekonomik", "hizli"];
const POSTA = ["34100", "06500", "35400", "65100"];

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_KODU[i % TARIFE_KODU.length],
    };
  }
}
```

İlk düzende her kayıt kendi tarife nesnesini kurar. Bu, tabloları paylaşmamanın en doğal
yoludur: kayıt kendi kendine yeter, hiçbir şeyi ortak kullanmaz.

```js
// kopyali/kur.mjs — her gonderi kendi tarife kopyasini tasir
import { KADEME_TABLO, KATSAYI_TABLO } from "../veri.mjs";

let uretilen = 0;
export const uretilenTarife = () => uretilen;

export function kur(satirlar) {
  const liste = [];
  for (const s of satirlar) {
    uretilen += 1;
    liste.push({
      kod: s.kod,
      agirlik: s.agirlik,
      postaKodu: s.postaKodu,
      tarife: {
        kod: s.tarifeKodu,
        kademeler: KADEME_TABLO[s.tarifeKodu].map(([u, f]) => [u, f]),
        katsayilar: { ...KATSAYI_TABLO },
        paraBirimi: "kurus",
      },
    });
  }
  return liste;
}

export function ucret(g) {
  const k = g.tarife.kademeler.find(([ust]) => g.agirlik <= ust) ?? [0, 24990];
  return Math.round((k[1] * (g.tarife.katsayilar[g.postaKodu.slice(0, 2)] ?? 165)) / 100);
}
```

#### Çözüm: İç Durumu Paylaşan Bir Havuz

Sinek siklet fabrikası aynı iç durum için tek nesne döndürür. Nesne **değiştirilemez** olmak
zorundadır; gerekçesi aşağıda ölçülüyor.

```js
// paylasimli/tarife.mjs — sinek siklet fabrikasi: ayni tarife kodu icin tek nesne
import { KADEME_TABLO, KATSAYI_TABLO } from "../veri.mjs";

const havuz = new Map();
let uretilen = 0;

export const uretilenTarife = () => uretilen;
export const havuzBoyu = () => havuz.size;

export function tarifeAl(kod) {
  let t = havuz.get(kod);
  if (t === undefined) {
    uretilen += 1;
    t = Object.freeze({
      kod,
      kademeler: Object.freeze(KADEME_TABLO[kod].map(([u, f]) => Object.freeze([u, f]))),
      katsayilar: Object.freeze({ ...KATSAYI_TABLO }),
      paraBirimi: "kurus",
    });
    havuz.set(kod, t);
  }
  return t;
}
```

```js
// paylasimli/kur.mjs — gonderi tarifeye basvuru tutar; ic durum paylasilir, dis durum satirda kalir
import { tarifeAl, uretilenTarife, havuzBoyu } from "./tarife.mjs";

export { uretilenTarife, havuzBoyu };

export function kur(satirlar) {
  const liste = [];
  for (const s of satirlar) {
    liste.push({ kod: s.kod, agirlik: s.agirlik, postaKodu: s.postaKodu, tarife: tarifeAl(s.tarifeKodu) });
  }
  return liste;
}

export function ucret(g) {
  const k = g.tarife.kademeler.find(([ust]) => g.agirlik <= ust) ?? [0, 24990];
  return Math.round((k[1] * (g.tarife.katsayilar[g.postaKodu.slice(0, 2)] ?? 165)) / 100);
}
```

Ücret hesabının gövdesi iki düzende aynıdır; değişen şey `g.tarife`nin kaça ait olduğudur.

```js
// kosum.mjs — iki duzen ayni toplam ucreti mi veriyor, kac tarife nesnesi kuruluyor
import { hamSatirlar } from "./veri.mjs";
import * as kopyali from "./kopyali/kur.mjs";
import * as paylasimli from "./paylasimli/kur.mjs";

const N = 20000;
const topla = (duzen) => duzen.kur(hamSatirlar(N)).reduce((t, g) => t + duzen.ucret(g), 0);

const a = topla(kopyali), b = topla(paylasimli);
console.log(`kopyali   : toplam=${a}  tarife nesnesi=${kopyali.uretilenTarife()}`);
console.log(`paylasimli: toplam=${b}  tarife nesnesi=${paylasimli.uretilenTarife()}`);
console.log(`toplam farki = ${a - b}  gonderi = ${N}`);
```

```
kopyali   : toplam=463093345  tarife nesnesi=20000
paylasimli: toplam=463093345  tarife nesnesi=3
toplam farki = 0  gonderi = 20000
```

Yirmi bin gönderi için kopyalı düzen 20.000 tarife nesnesi kurdu, paylaşımlı düzen 3. Toplam
ücret kuruşu kuruşuna aynı. Nesne sayısı ortamdan bağımsız, kesin bir sayıdır: iç durum çeşidi
kadar nesne kurulur.

#### Yığın Ölçümü

İkinci ölçü yığın kullanımıdır ve **ortama bağlıdır**: aşağıdaki megabayt değerleri bu
makinede `node` v24.18.0 ile alınmıştır; sürüm, işletim sistemi ve çöp toplayıcı ayarları
değiştiğinde sayılar değişir. Değişmeyen şey büyüklük mertebesidir. Ölçüm için çöp toplayıcının
elle çağrılabilmesi gerekir; bu yüzden betik `--expose-gc` seçeneğiyle çalıştırılır.

```js
// bellek.mjs — bir duzeni kurup yigin kullanimini olcer; node --expose-gc ile calistirilir
import { hamSatirlar } from "./veri.mjs";

if (globalThis.gc === undefined) {
  console.error("bu betik 'node --expose-gc bellek.mjs <duzen> <adet>' ile calistirilir");
  process.exit(1);
}

const duzen = process.argv[2];
const adet = Number(process.argv[3]);
const { kur, ucret, uretilenTarife } = await import(`./${duzen}/kur.mjs`);

globalThis.gc();
const once = process.memoryUsage().heapUsed;
const liste = kur(hamSatirlar(adet));
globalThis.gc();
const sonra = process.memoryUsage().heapUsed;
const toplam = liste.reduce((t, g) => t + ucret(g), 0);
console.log(`${duzen.padEnd(11)} gonderi=${liste.length}  tarife nesnesi=${uretilenTarife()}  ` +
  `yigin=${Math.round((sonra - once) / 1048576)} MB  toplam=${toplam}`);
```

```sh
node --expose-gc bellek.mjs kopyali 200000
node --expose-gc bellek.mjs paylasimli 200000
```

```
kopyali     gonderi=200000  tarife nesnesi=200000  yigin=208 MB  toplam=4631039370
paylasimli  gonderi=200000  tarife nesnesi=3  yigin=20 MB  toplam=4631039370
```

İki yüz bin gönderi için 208 megabayta karşı 20 megabayt; oran on katın biraz üstünde. Kalan
20 megabayt dış durumun kendisidir — gönderi kodu, ağırlık, posta kodu — ve paylaşımla
küçültülemez, çünkü her kayıtta farklıdır. Ölçüm iki ayrı süreçte yapıldı; aynı süreçte iki
düzeni birden kurmak, birinci düzenin bıraktığı nesnelerin ikinci ölçüme karışması riskini
taşır.

Toplam ücret her iki koşumda da aynı çıktı; paylaşım sonucu değiştirmedi.

#### Bedel: Değiştirilemezlik ve Paylaşımın Genişliği

Paylaşılan nesnenin en görünür bedeli, artık kimseye ait olmamasıdır.

```js
// bedel.mjs — paylasimin bedeli: degistirilebilirlik, paylasim genisligi, nesne kimligi
import { hamSatirlar } from "./veri.mjs";
import * as kopyali from "./kopyali/kur.mjs";
import * as paylasimli from "./paylasimli/kur.mjs";

const N = 20000;
const k = kopyali.kur(hamSatirlar(N));
const p = paylasimli.kur(hamSatirlar(N));

const oncekiK = k.map(kopyali.ucret);
k[0].tarife.katsayilar["34"] = 300;
console.log(`kopyali   : bir kaydin tablosu degisti -> degisen sonuc = ` +
  `${k.map(kopyali.ucret).filter((v, i) => v !== oncekiK[i]).length} / ${N}`);

try {
  p[0].tarife.katsayilar["34"] = 300;
  console.log("paylasimli: ayni degisiklik kabul edildi");
} catch (e) {
  console.log(`paylasimli: ayni degisiklik reddedildi (${e.constructor.name})`);
}

console.log(`paylasimli: ayni nesneyi paylasan gonderi = ${p.filter((g) => g.tarife === p[0].tarife).length} / ${N}`);
console.log(`kopyali   : ayni nesneyi paylasan gonderi = ${k.filter((g) => g.tarife === k[0].tarife).length} / ${N}`);
const cesit = paylasimli.havuzBoyu();
console.log(`ic durum cesidi = ${cesit}  paylasim orani = ${Math.round(N / cesit)} gonderi/nesne`);
```

```
kopyali   : bir kaydin tablosu degisti -> degisen sonuc = 1 / 20000
paylasimli: ayni degisiklik reddedildi (TypeError)
paylasimli: ayni nesneyi paylasan gonderi = 6667 / 20000
kopyali   : ayni nesneyi paylasan gonderi = 1 / 20000
ic durum cesidi = 3  paylasim orani = 6667 gonderi/nesne
```

Kopyalı düzende bir kaydın tablosunu değiştirmek yalnız o kaydı bozdu: 1/20000. Paylaşımlı
düzende aynı değişiklik `Object.freeze` sayesinde reddedildi ve `TypeError` verdi. Dondurma
olmasaydı değişiklik kabul edilecek ve aynı nesneyi paylaşan 6667 gönderinin ücreti sessizce
sapacaktı. Paylaşım oranı hem kazancın hem riskin ölçüsüdür: her nesne 6667 kayda hizmet
ediyor, dolayısıyla o nesnedeki bir hata 6667 kayda yayılır.

İkinci bedel nesne kimliğinin anlam değiştirmesidir. Kopyalı düzende `g1.tarife === g2.tarife`
karşılaştırması yalnız aynı kayıt için doğrudur; paylaşımlı düzende aynı tarife kodunu taşıyan
her gönderi için doğrudur. Kimlik eşitliğine dayanarak "bu iki gönderi aynı tarife nesnesini
kullanıyorsa aynı müşteriye aittir" gibi bir çıkarım yapan kod, paylaşıma geçildiğinde yanılır.

Üçüncü bedel havuzun kendisidir: bir dosya, bir eşleme yapısı ve her kayıt kurulurken bir arama.
Havuz ayrıca hiçbir zaman boşalmaz; iç durum çeşidi sınırsızsa havuz sınırsız büyür.

Buradan uygulanmama koşulu doğrudan çıkar. Kazanç, kayıt sayısının iç durum çeşidine oranıyla
büyür: bu koşumda 200.000/3. Oran 1'e yaklaşırsa — her kaydın iç durumu kendine özgüyse —
paylaşılacak hiçbir şey yoktur, havuz kayıt sayısı kadar nesne tutar ve üstüne eşleme yükü
eklenir. Kayıt sayısı küçükse (yüzlerce) 200 megabaytlık fark yüz kilobayta iner ve
değiştirilemezlik kısıtı karşılıksız kalır.

#### Özet

- Sinek sikletin çözdüğü problem, kayıt sayısı kadar kopyalanan ama kayda göre değişmeyen
  bilginin belleği doldurmasıdır; çözüm iç durumu paylaşılan nesnede toplamak, dış durumu
  kayıtta bırakmaktır.
- Yirmi bin gönderi için kurulan tarife nesnesi 20.000'den 3'e indi ve toplam ücret kuruşu
  kuruşuna aynı kaldı; nesne sayısı ortamdan bağımsız bir ölçüdür.
- İki yüz bin gönderide yığın kullanımı 208 megabayttan 20 megabayta indi; bu değerler bu
  makinede `node` v24.18.0 ile ölçüldü ve ortam değiştiğinde değişir, oran ise mertebe olarak
  korunur.
- Paylaşılan nesne değiştirilemez olmak zorundadır: kopyalı düzende bir tablo değişikliği 1
  kaydı bozdu, paylaşımlı düzende aynı değişiklik dondurma sayesinde reddedildi; kabul edilseydi
  aynı nesneyi paylaşan 6667 kaydı bozacaktı.
- Kazanç kayıt sayısının iç durum çeşidine oranıyla büyür; oran 1'e yakınsa ya da kayıt sayısı
  küçükse kalıp yalnız havuz yükü ve değiştirilemezlik kısıtı ekler.

#### Sonraki Adım

Sinek siklette bir nesneye çok sayıda kayıt aynı başvuruyla ulaştı; nesne paylaşıldı ama erişim
serbestti — isteyen `tarife` alanını okuyup doğrudan kullanabiliyordu. Bir sonraki problem
erişimin kendisini konu edinir. Tarife tablosu uzak bir kaynaktan geliyorsa ilk erişime kadar
hiç yüklenmemesi, aynı tablonun ikinci kez istenmesinde tekrar getirilmemesi ve belli
tarifelere erişimin kapatılabilmesi gerekir. Üçü de asıl nesnenin gövdesine dokunmadan, aynı
arayüzü sunan bir nesnenin araya girmesiyle çözülür. Sonraki ders kaynağa giden çağrı sayısını
iki düzende sayar, vekil kalıbını yazar ve bedelini bir dolaylılık basamağı ile bayat veri
riski cinsinden ölçer.

### Vekil

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/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.

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.

```js
// 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,
  };
}
```

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

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

```js
// 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,
  };
};
```

```js
// 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);
    },
  };
};
```

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

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

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

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

## Davranışsal Kalıplar

Nesneler arasındaki sorumluluk dağılımının ve iletişimin düzenlenmesi: algoritmanın çalışma zamanında değiştirilmesi, olay bildirimiyle gevşek bağlanma, istemin nesneleştirilip geri alınabilmesi, değişmeyen iskeletle değişen adımların ayrılması, durum makinesinin nesnelerle ifadesi, isteğin işleyiciler arasında aktarılması, gezinme ile işlemin ayrılması, etkileşimin merkezileştirilmesi, küçük dil ve boş davranış kalıpları ile gereksiz dolaylılığın maliyeti.

### Strateji

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/strateji

Ücret hesaplama algoritmasının çağrı yerine gömüldüğü tasarım ile tarifenin parametre olarak geçildiği tasarımın karşılaştırılması: tek koşumda denenebilen algoritma sayısı, yeni tarife eklendiğinde düzenlenen dosya ve satır sayısı, kalıbın bedeli olarak eklenen dosya sayısı ve çağrı yerinden izlenmesi gereken olası gövde sayısı.

Yapısal kalıplar nesnelerin nasıl birleştirileceğini düzenledi: bağdaştırıcı uyumsuz iki
arayüzü köprüledi, dekoratör davranışı katmanladı, bileşik ağacı tek biçim ele aldı, vekil
ise araya girip çağrıyı **aynı arayüzle** ilerisine geçirdi. Vekilde araya giren nesnenin işi
çağrıyı geçirmekti; hesabın kendisi değişmiyordu. Sıradaki soru birleşme değil: aynı arayüzün
arkasındaki **hesabın kendisi** çalışma zamanında değiştirilebilir mi?

Ücretlendirme kitaplığında bu somut bir istektir. Aynı gönderi kümesi standart tarifeyle,
ekspres tarifeyle ve hacimden türetilen ücretli ağırlık tarifesiyle fiyatlanmak istenir; üçü
de aynı koşumda, aynı veriyle. **Strateji** (strategy) kalıbı bunu, algoritmayı bir nesneye
alıp çağrı yerine parametre olarak geçirerek yapar. Koşul zinciri, arama tablosu ve çok
biçimlilik arasındaki seçim Temiz Kod ve Programlama Paradigmaları kurslarında ölçülmüştü; bu
derste ölçülen şey başka: **hangi dosyaların düzenlenmesi gerektiği** ve **tek koşumda kaç
algoritmanın yan yana durabildiği**.

#### İki Tasarım

Ortak veri her iki sürümde aynıdır. Tutarlar kuruş cinsindendir, ağırlık kilogram, hacim
desimetreküp.

```sh
mkdir -p gomulu strateji
```

```js
// veri.mjs — ucretlendirilecek gonderiler (agirlik kg, hacim dm3, tutarlar kurus)
export const GONDERILER = [
  { kod: "GN-1", agirlik: 2, hacim: 8, bolge: "1" },
  { kod: "GN-2", agirlik: 6, hacim: 60, bolge: "2" },
  { kod: "GN-3", agirlik: 14, hacim: 20, bolge: "3" },
  { kod: "GN-4", agirlik: 1, hacim: 3, bolge: "2" },
  { kod: "GN-5", agirlik: 22, hacim: 180, bolge: "3" },
];
```

Birinci tasarımda formül, onu çağıran modülün ithal ettiği tek gövdenin içindedir. Toplu
fiyatlama hangi algoritmayı çalıştırdığını seçmez; ithal satırı seçmiştir.

```js
// gomulu/ucret.mjs — tarife formulu govdenin icinde sabit
const BOLGE_KATSAYI = { "1": 100, "2": 130, "3": 175 };

export function ucret(gonderi) {
  const ham = 2500 + 420 * Math.ceil(gonderi.agirlik);
  return Math.max(Math.round((ham * BOLGE_KATSAYI[gonderi.bolge]) / 100), 3900);
}
```

```js
// gomulu/toplu.mjs — toplu fiyatlama; algoritma ithalle sabitlenmis
import { ucret } from "./ucret.mjs";

export const topluUcret = (gonderiler) =>
  gonderiler.map((g) => ({ kod: g.kod, tutar: ucret(g) }));
```

İkinci tasarımda her tarife kendi dosyasında bir nesnedir. Nesnelerin tek ortak sözleşmesi
`ucret(gonderi)` yöntemidir; paylaştıkları tek veri bölge katsayılarıdır.

```js
// strateji/bolge.mjs — bolge katsayilari, tarifelerin paylastigi tek veri
export const BOLGE_KATSAYI = { "1": 100, "2": 130, "3": 175 };
export const bolgele = (ham, bolge) => Math.round((ham * BOLGE_KATSAYI[bolge]) / 100);
```

```js
// strateji/tarife-standart.mjs
import { bolgele } from "./bolge.mjs";

export const standart = {
  ad: "standart",
  ucret(gonderi) {
    return Math.max(bolgele(2500 + 420 * Math.ceil(gonderi.agirlik), gonderi.bolge), 3900);
  },
};
```

```js
// strateji/tarife-ekspres.mjs
import { bolgele } from "./bolge.mjs";

export const ekspres = {
  ad: "ekspres",
  ucret(gonderi) {
    return Math.max(bolgele(4000 + 640 * Math.ceil(gonderi.agirlik), gonderi.bolge), 6500);
  },
};
```

```js
// strateji/tarife-hacimsel.mjs — ucretli agirlik hacimden turetilir
import { bolgele } from "./bolge.mjs";

export const hacimsel = {
  ad: "hacimsel",
  ucret(gonderi) {
    const ucretliAgirlik = Math.max(Math.ceil(gonderi.agirlik), Math.ceil(gonderi.hacim / 5));
    return Math.max(bolgele(2500 + 420 * ucretliAgirlik, gonderi.bolge), 3900);
  },
};
```

```js
// strateji/toplu.mjs — tarife parametre olarak gelir
export const topluUcret = (gonderiler, tarife) =>
  gonderiler.map((g) => ({ kod: g.kod, tutar: tarife.ucret(g) }));
```

Toplu fiyatlama artık hiçbir tarifeyi ithal etmiyor. Bildiği tek şey, aldığı nesnenin bir
`ucret` yöntemi taşıdığıdır.

#### Tek Koşumda Kaç Algoritma

İlk ölçü, aynı süreçte aynı veriye kaç ayrı algoritmanın uygulanabildiğidir. Sürücü betik
iki sürümü de çağırır.

```js
// kosum.mjs — tek kosumda kac tarife denenebiliyor
import { GONDERILER } from "./veri.mjs";
import { topluUcret as gomuluToplu } from "./gomulu/toplu.mjs";
import { topluUcret as stratejiToplu } from "./strateji/toplu.mjs";
import { standart } from "./strateji/tarife-standart.mjs";
import { ekspres } from "./strateji/tarife-ekspres.mjs";
import { hacimsel } from "./strateji/tarife-hacimsel.mjs";

const yaz = (etiket, satirlar) =>
  console.log(`${etiket.padEnd(18)} ${satirlar.map((s) => `${s.kod}=${s.tutar}`).join(" ")}`);

const gomuluSonuc = [gomuluToplu(GONDERILER)];
yaz("gomulu", gomuluSonuc[0]);

const stratejiSonuc = [standart, ekspres, hacimsel].map((t) => {
  const satirlar = stratejiToplu(GONDERILER, t);
  yaz(`strateji/${t.ad}`, satirlar);
  return satirlar;
});

console.log(`gomulu   : tek kosumda denenen tarife = ${gomuluSonuc.length}`);
console.log(`strateji : tek kosumda denenen tarife = ${stratejiSonuc.length}`);
```

```
gomulu             GN-1=3900 GN-2=6526 GN-3=14665 GN-4=3900 GN-5=20545
strateji/standart  GN-1=3900 GN-2=6526 GN-3=14665 GN-4=3900 GN-5=20545
strateji/ekspres   GN-1=6500 GN-2=10192 GN-3=22680 GN-4=6500 GN-5=31640
strateji/hacimsel  GN-1=3900 GN-2=9802 GN-3=14665 GN-4=3900 GN-5=30835
gomulu   : tek kosumda denenen tarife = 1
strateji : tek kosumda denenen tarife = 3
```

Bir ve üç. Gömülü sürümde ikinci bir tarifeyi denemenin yolu kaynağı düzenleyip süreci
yeniden başlatmaktır; üç tarifeyi karşılaştıran bir rapor üç ayrı koşum ve arada iki
düzenleme demektir. Strateji sürümünde üç sonuç aynı süreçte yan yana durur, bu yüzden
karşılaştırma raporu bir işlev çağrısıdır. Çıktının ilk iki satırının aynı olması ölçümün
geçerlilik koşuludur: strateji sürümü standart tarifede gömülü sürümle birebir aynı tutarı
üretiyor, yani karşılaştırılan iki tasarım aynı davranışı taşıyor.

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

İkinci ölçü değişim maliyetidir. Ekonomik adında dördüncü bir tarife isteniyor: tabanı 1800,
kademesi 300, asgari ücreti 3200. İki ağaç kopyalanır ve istek ikisine de uygulanır. Gömülü
sürümde gövdenin kendisi ve onu çağıran modülün imzası; strateji sürümünde tek bir yeni
dosya.

```sh
cp -r gomulu gomulu-yeni
cp -r strateji strateji-yeni

cat > gomulu-yeni/ucret.mjs <<'SON'
// gomulu/ucret.mjs — tarife formulu govdenin icinde sabit
const BOLGE_KATSAYI = { "1": 100, "2": 130, "3": 175 };

export function ucret(gonderi, mod = "standart") {
  const taban = mod === "ekonomik" ? 1800 : 2500;
  const kademe = mod === "ekonomik" ? 300 : 420;
  const asgari = mod === "ekonomik" ? 3200 : 3900;
  const ham = taban + kademe * Math.ceil(gonderi.agirlik);
  return Math.max(Math.round((ham * BOLGE_KATSAYI[gonderi.bolge]) / 100), asgari);
}
SON

cat > gomulu-yeni/toplu.mjs <<'SON'
// gomulu/toplu.mjs — toplu fiyatlama; algoritma ithalle sabitlenmis
import { ucret } from "./ucret.mjs";

export const topluUcret = (gonderiler, mod) =>
  gonderiler.map((g) => ({ kod: g.kod, tutar: ucret(g, mod) }));
SON

cat > strateji-yeni/tarife-ekonomik.mjs <<'SON'
// strateji/tarife-ekonomik.mjs — var olan dosyalara dokunulmadi
import { bolgele } from "./bolge.mjs";

export const ekonomik = {
  ad: "ekonomik",
  ucret(gonderi) {
    return Math.max(bolgele(1800 + 300 * Math.ceil(gonderi.agirlik), gonderi.bolge), 3200);
  },
};
SON

for k in gomulu strateji; do
  duzenlenen=$(diff -rq "$k" "$k-yeni" | grep -c '^Files')
  eklenen=$(diff -rq "$k" "$k-yeni" | grep -c '^Only in')
  satir=$(diff -r -u "$k" "$k-yeni" | grep '^[+-][^+-]' | grep -vc '^[+-]//')
  echo "$k: duzenlenen dosya=$duzenlenen  yeni dosya=$eklenen  duzenlenen satir=$satir"
done
echo "dosya sayisi: gomulu=$(ls gomulu | wc -l | tr -d ' ')  strateji=$(ls strateji | wc -l | tr -d ' ')"
echo "cagri yerinden olasi govde: gomulu=1  strateji=$(grep -l 'ucret(gonderi)' strateji/tarife-*.mjs | wc -l | tr -d ' ')"
```

```
gomulu: duzenlenen dosya=2  yeni dosya=0  duzenlenen satir=13
strateji: duzenlenen dosya=0  yeni dosya=1  duzenlenen satir=0
dosya sayisi: gomulu=2  strateji=5
cagri yerinden olasi govde: gomulu=1  strateji=3
```

Gömülü sürümde iki dosya ve on üç satır düzenlendi; bu satırların bir kısmı çalışan
tarifelerin de geçtiği gövdedir, yani dördüncü tarifeyi eklerken ilk üçünün davranışı riske
girer. Strateji sürümünde düzenlenen dosya sıfırdır. Açık–kapalı ilkesinin ölçüsü tam olarak
budur ve strateji kalıbı bu ilkeyi tarife ekseninde sağlar.

#### Kalıbın Bedeli

Aynı çıktının son iki satırı bedeli veriyor. Dosya sayısı 2'den 5'e çıktı: üç tarife dosyası
ve paylaşılan bölge modülü. Bedelin ikinci kalemi okuma maliyetidir. Gömülü sürümde `ucret`
çağrısından tek bir gövdeye gidilir; strateji sürümünde `tarife.ucret(g)` çağrısından
statik olarak üç ayrı gövdeye gidilebilir. Hangisinin çalıştığını anlamak için nesnenin nerede
üretildiğine, yani çağrı zincirinin bir üst basamağına bakmak gerekir. Bu, bir dolaylılık
basamağıdır ve sayısı üçtür.

Denge şöyle kurulur: tarife sayısı arttıkça düzenlenen satır sayısı gömülü sürümde büyür,
strateji sürümünde sıfır kalır; dosya sayısı ise gömülü sürümde sabit kalır, strateji
sürümünde tarife başına bir artar. İki eğrinin kesiştiği yer kalıbın karşılığını verdiği
eşiktir. Tek tarifesi olan bir kitaplıkta strateji kalıbı üç dosya ve bir dolaylılık basamağı
ekler, karşılığında sıfır kazanç verir; bu durumun ölçümü konunun son dersindedir.

#### Özet

- Strateji kalıbı, bir hesabı ayrı bir nesneye alıp çağrı yerine parametre olarak geçirir;
  ölçülen kazanç, algoritmanın çalışma zamanında değiştirilebilmesidir.
- Aynı veri üzerinde tek koşumda denenebilen algoritma sayısı gömülü sürümde 1, strateji
  sürümünde 3 çıktı; strateji sürümü standart tarifede birebir aynı tutarları üretti.
- Dördüncü tarife eklendiğinde gömülü sürümde 2 dosya ve 13 satır düzenlendi, strateji
  sürümünde 0 dosya düzenlendi ve 1 dosya eklendi.
- Bedel iki kalemdir: dosya sayısı 2'den 5'e çıktı ve çağrı yerinden statik olarak izlenmesi
  gereken olası gövde sayısı 1'den 3'e yükseldi.
- Kalıp, tarife sayısı arttıkça karşılığını verir; tek gerçekleştirimde eklenen dosya ve
  dolaylılık karşılıksız kalır.

#### Sonraki Adım

Tarife nesnesi bir hesabı taşıyor ve çağıran onu doğrudan çağırıyor: bağ tek yönlü ve
açıktır. Kitaplığın başka bir yerinde ise ilişki ters yönde kuruluyor. Bir teslimatın durumu
değiştiğinde günlük satırı yazılması, müşteriye bildirim düşülmesi, ölçüm sayacının
artırılması ve faturalama kaydının güncellenmesi gerekiyor. Bu dört işi teslimat modülünün
kendisi çağırırsa teslimat dördünü de ithal etmek zorunda kalır ve beşinci iş eklendiğinde
yine düzenlenir. Sonraki ders bu durumu giden bağ sayısı ve ithal kapanışı boyutuyla ölçer,
sonra bildirimi tersine çevirip aynı sayıları yeniden hesaplar.

### Gözlemci

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/gozlemci

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.

### Komut

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/komut

Gönderi üzerindeki işlemlerin doğrudan çağrı olarak yazılması ile nesneye çevrilmesinin karşılaştırılması: geri alınabilen işlem sayısı, denetim izi kaydı sayısı, izi doğrudan sürümde üretmek için düzenlenen işlem gövdesi sayısı ve ortaya çıkan ortak bağlaşım, kalıbın bedeli olarak dosya sayısı ile çağrı derinliği.

Gözlemci olmuş bir şeyi haber veriyordu: bildirim gönderildiği anda durum çoktan değişmişti ve
geri dönüş yoktu. Kitaplığın operatör tarafında tersi isteniyor. Bir gönderiye indirim
uygulanması, rotasının değiştirilmesi ve iptali operatör kararıyla yapılıyor; yanlış uygulanan
bir işlemin geri alınması gerekiyor. Bugün bu üç işlem doğrudan alan değiştiren çağrılardır ve
çağrı bittiğinde eski değer kaybolmuştur.

**Komut** (command) kalıbı, bir işlemi çağrı olmaktan çıkarıp nesne yapar: ne yapılacağı ve
hangi parametreyle yapılacağı bir nesnenin içinde durur, çağrılmadan önce elde tutulabilir,
sıraya konabilir, kaydedilebilir ve tersi aynı nesneden istenebilir. Ölçülecek sayılar geri
alınabilen işlem sayısı, denetim izi kaydı sayısı ve kesişen bir iş eklendiğinde düzenlenen
işlem gövdesi sayısıdır. Bedel tarafında dosya sayısı ve çağrı derinliği durur.

#### Alan Nesnesi ve İki Düzen

Alan nesnesi iki sürümde ortaktır. Her yazma işleminde kendi dosyalarımıza ait çağrı
çerçevesi sayısını kaydeder; bu sayı dolaylılık basamağının koşumdaki karşılığıdır.

```sh
mkdir -p dogrudan komut
```

```js
// gonderi.mjs — alan nesnesi; her yazma isleminde cagri derinligini kaydeder
export const gonderi = (kod) => {
  const durum = { kod, indirim: 0, rota: "R1", iptal: "hayir" };
  const derinlikler = [];
  return {
    durum,
    derinlikler,
    yaz(alan, deger) {
      durum[alan] = deger;
      derinlikler.push(new Error().stack.split("\n").filter((s) => s.includes(".mjs:")).length);
    },
    ozet: () => `${durum.kod} indirim=${durum.indirim} rota=${durum.rota} iptal=${durum.iptal}`,
  };
};
```

Birinci düzen üç işlemi üç işlev olarak yazar. Bir işlemin tersi hiçbir yerde tanımlı değildir;
`indirimUygula` çağrıldıktan sonra eski oranı bilen kimse kalmaz.

```js
// dogrudan/islem.mjs — islemler alani dogrudan yazar, tersi yoktur
export const indirimUygula = (g, oran) => g.yaz("indirim", oran);
export const rotaDegistir = (g, rota) => g.yaz("rota", rota);
export const iptalEt = (g) => g.yaz("iptal", "evet");
```

İkinci düzende her işlem bir nesne üretir. Nesne, uygulanmadan önceki değeri kendi
kapanışında saklar ve `geriAl` yöntemiyle onu geri yazar.

```js
// komut/indirim.mjs
export const indirimKomutu = (oran) => {
  let onceki;
  return {
    ad: `indirim(${oran})`,
    uygula(g) {
      onceki = g.durum.indirim;
      g.yaz("indirim", oran);
    },
    geriAl(g) {
      g.yaz("indirim", onceki);
    },
  };
};
```

```js
// komut/rota.mjs
export const rotaKomutu = (rota) => {
  let onceki;
  return {
    ad: `rota(${rota})`,
    uygula(g) {
      onceki = g.durum.rota;
      g.yaz("rota", rota);
    },
    geriAl(g) {
      g.yaz("rota", onceki);
    },
  };
};
```

```js
// komut/iptal.mjs
export const iptalKomutu = () => {
  let onceki;
  return {
    ad: "iptal",
    uygula(g) {
      onceki = g.durum.iptal;
      g.yaz("iptal", "evet");
    },
    geriAl(g) {
      g.yaz("iptal", onceki);
    },
  };
};
```

Üç nesnenin ortak sözleşmesi `ad`, `uygula` ve `geriAl` üçlüsüdür. Bu sözleşme, komutu
çalıştıran nesnenin komutun ne yaptığını bilmeden onu yönetmesine yeter. Çalıştırıcı geçmiş
yığıtını tutar ve her çağrıyı denetim izine yazar.

```js
// komut/calistirici.mjs — istemleri calistirir, gecmis yigitini ve denetim izini tutar
export const calistirici = () => {
  const gecmis = [];
  const iz = [];
  return {
    iz,
    calistir(komut, g) {
      komut.uygula(g);
      gecmis.push([komut, g]);
      iz.push(`uygula  ${komut.ad}`);
    },
    geriAl(adet) {
      let sayi = 0;
      while (sayi < adet && gecmis.length > 0) {
        const [komut, g] = gecmis.pop();
        komut.geriAl(g);
        iz.push(`geri-al ${komut.ad}`);
        sayi += 1;
      }
      return sayi;
    },
  };
};
```

#### Beş İşlem, Üç Geri Alma

Sürücü betik aynı beş işlemi iki sürüme de uygular, sonra üçünü geri almayı dener ve son
durumları karşılaştırır.

```js
// kosum.mjs — bes islem uygulanir, ucu geri alinir; iki surum karsilastirilir
import { gonderi } from "./gonderi.mjs";
import { indirimUygula, rotaDegistir, iptalEt } from "./dogrudan/islem.mjs";
import { indirimKomutu } from "./komut/indirim.mjs";
import { rotaKomutu } from "./komut/rota.mjs";
import { iptalKomutu } from "./komut/iptal.mjs";
import { calistirici } from "./komut/calistirici.mjs";

const a = gonderi("GN-7");
indirimUygula(a, 10);
rotaDegistir(a, "R2");
indirimUygula(a, 15);
rotaDegistir(a, "R3");
iptalEt(a);
console.log(`dogrudan  bes islem sonrasi : ${a.ozet()}`);
console.log(`dogrudan  geri alinan islem : 0`);

const b = gonderi("GN-7");
const k = calistirici();
for (const komut of [indirimKomutu(10), rotaKomutu("R2"), indirimKomutu(15), rotaKomutu("R3"), iptalKomutu()]) {
  k.calistir(komut, b);
}
console.log(`komut     bes islem sonrasi : ${b.ozet()}`);
const geri = k.geriAl(3);
console.log(`komut     geri alinan islem : ${geri}`);
console.log(`komut     geri alma sonrasi : ${b.ozet()}`);
console.log(`cagri derinligi (dogrudan/komut): ${a.derinlikler[0]} / ${b.derinlikler[0]}`);
console.log(`denetim izi kaydi (dogrudan/komut): 0 / ${k.iz.length}`);
```

```
dogrudan  bes islem sonrasi : GN-7 indirim=15 rota=R3 iptal=evet
dogrudan  geri alinan islem : 0
komut     bes islem sonrasi : GN-7 indirim=15 rota=R3 iptal=evet
komut     geri alinan islem : 3
komut     geri alma sonrasi : GN-7 indirim=10 rota=R2 iptal=hayir
cagri derinligi (dogrudan/komut): 3 / 4
denetim izi kaydi (dogrudan/komut): 0 / 8
```

İki satır ölçümün geçerlilik koşulunu veriyor: beş işlem sonrası iki sürümün özeti birebir
aynı. Sonrası kazançtır. Geri alınan işlem sayısı 0'a karşı 3; geri alma sonrası durum,
ilk iki işlemin uygulandığı duruma birebir dönmüş. Denetim izi 0'a karşı 8 kayıt taşıyor —
beş uygulama ve üç geri alma. Bu sekiz kayıt için hiçbir işlem gövdesine dokunulmadı; kayıt,
komutun `ad` alanını okuyan çalıştırıcıda üretildi.

Çağrı derinliği bedeli veriyor: 3'e karşı 4. Bu sayılar kendi dosyalarımıza ait çağrı
çerçevelerini sayar; taban değer koşum betiğinin çerçevesini de içerdiği için mutlak sayı
önemli değil, iki sürüm arasındaki bir basamak farkı önemlidir. Operatör isteminden alan
yazmasına giden yol bir çağrı uzadı.

#### Denetim İzini Doğrudan Sürüme Eklemek

Sekiz kayıtlık izi doğrudan sürümde de üretmek istenirse üç işlem gövdesinin de düzenlenmesi
gerekir; kaydın tutulacağı yer de bir modül düzeyi dizi olmak zorundadır, çünkü üç işlevin
paylaşacağı başka bir yer yoktur.

```js
// denetim.mjs — denetim izini iki surume de ekler ve degisimi olcer
import { cpSync, readFileSync, writeFileSync } from "node:fs";

cpSync("dogrudan", "dogrudan-yeni", { recursive: true });
writeFileSync(
  "dogrudan-yeni/islem.mjs",
  `// dogrudan/islem.mjs — islemler alani dogrudan yazar, tersi yoktur
export const iz = [];
export const indirimUygula = (g, oran) => {
  iz.push(\`uygula  indirim(\${oran})\`);
  g.yaz("indirim", oran);
};
export const rotaDegistir = (g, rota) => {
  iz.push(\`uygula  rota(\${rota})\`);
  g.yaz("rota", rota);
};
export const iptalEt = (g) => {
  iz.push("uygula  iptal");
  g.yaz("iptal", "evet");
};
`,
);
console.log(`duzenlenen islem govdesi = ${(readFileSync("dogrudan-yeni/islem.mjs", "utf8").match(/iz\.push/g) ?? []).length}`);
console.log(`yeni modul duzeyi paylasilan durum = ${(readFileSync("dogrudan-yeni/islem.mjs", "utf8").match(/^export const iz = \[\];$/m) ?? []).length}`);
console.log(`komut surumunde duzenlenen islem govdesi = 0`);
```

```
duzenlenen islem govdesi = 3
yeni modul duzeyi paylasilan durum = 1
komut surumunde duzenlenen islem govdesi = 0
```

Üç gövdeye karşı sıfır gövde. Sayının yanında bir de tür farkı var: doğrudan sürüme eklenen
`iz` dizisi modül düzeyinde paylaşılan değiştirilebilir durumdur, yani tasarım ilkeleri
kursunda içerikten sonra en güçlü ikinci biçim olarak ölçülen **ortak bağlaşım**. Üç işlev bu
diziyi birlikte değiştirir; ikinci bir iz gerektiğinde ya da testte izin sıfırlanması
gerektiğinde üçü birlikte etkilenir. Komut sürümünde iz çalıştırıcı örneğine aittir, her koşum
kendi izini taşır ve paylaşılan modül durumu sayısı sıfır kalır.

#### Bedelin Sayımı

```sh
echo "dosya sayisi: dogrudan=$(ls dogrudan | wc -l | tr -d ' ')  komut=$(ls komut | wc -l | tr -d ' ')"
echo "tersini bilen dosya: dogrudan=0  komut=$(grep -l '^    geriAl(g) {$' komut/*.mjs | wc -l | tr -d ' ')"
echo "gecmis yigitini tutan dosya: komut=$(grep -l 'gecmis.push' komut/*.mjs | wc -l | tr -d ' ')"
```

```
dosya sayisi: dogrudan=1  komut=4
tersini bilen dosya: dogrudan=0  komut=3
gecmis yigitini tutan dosya: komut=1
```

Bir dosyaya karşı dört dosya. Kalıbın bedeli işlem başına bir dosya ve bir tiptir; kazancı ise
işlem sayısından bağımsızdır, çünkü geçmiş yığıtını tutan yer tektir. Dördüncü bir işlem
eklendiğinde bedel bir dosya artar, geri alma ve denetim izi ise kendiliğinden çalışır. Üç
satır bir de sınırı gösteriyor: tersini bilen dosya sayısı işlem sayısına eşit olmak zorundadır,
yani tersi tanımlanamayan bir işlem varsa kalıp geri alma kazancını vermez.

#### Özet

- Komut kalıbı bir işlemi nesneye çevirir; nesne ne yapılacağını, parametresini ve tersini
  taşır, bu yüzden uygulanmadan önce tutulabilir ve sonradan geri alınabilir.
- Aynı beş işlem iki sürümde birebir aynı durumu üretti; geri alınan işlem sayısı 0'a karşı 3
  çıktı ve geri alma sonrası durum ilk iki işlemin durumuna birebir döndü.
- Denetim izi komut sürümünde 8 kayıt üretti ve bunun için hiçbir işlem gövdesi düzenlenmedi;
  aynı izi doğrudan sürümde üretmek 3 gövdenin düzenlenmesini ve 1 modül düzeyi paylaşılan
  durumu, yani ortak bağlaşımı gerektirdi.
- Bedel: dosya sayısı 1'den 4'e çıktı, çağrı derinliği 3'ten 4'e yükseldi; tersini bilen dosya
  sayısı işlem sayısına eşit olmak zorundadır.
- Kalıbın kazancı işlem sayısından bağımsızdır, bedeli işlem sayısıyla doğrusal büyür.

#### Sonraki Adım

Üç komut aynı üç adımı aynı sırayla yapıyor: önceki değeri sakla, yeni değeri yaz, kaydı
tut. Sıra her komutta aynı, yalnız ortadaki adım değişiyor. Aynı tekrar kitaplığın ücret
hesaplama tarafında daha büyük ölçekte var: yurt içi, yurt dışı ve ekspres taşıyıcı için
ücretlendirme altı adımdan geçiyor ve üç dosya bu altı adımı ayrı ayrı sıralıyor. Sıraya bir
adım eklendiğinde üç dosya da düzenleniyor. Sonraki ders yinelenen satır sayısını ve adım
sırası değiştiğinde düzenlenen dosya sayısını ölçer, sonra iskeleti tek yere alıp aynı
sayıları yeniden hesaplar.

### Şablon Yöntem

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/sablon-yontem

Ücretlendirme adımlarının üç taşıyıcı dosyasında ayrı ayrı sıralanması ile iskeletin tek yere alınmasının karşılaştırılması: en az iki dosyada yinelenen satır sayısı, adım sırası sapmasının ürettiği tutar hatası, yedinci adım eklendiğinde düzenlenen dosya sayısı ve kalıbın bedeli olarak toplam satır artışı ile boş kanca sayısı.

Üç komut aynı üç adımı aynı sırayla yapıyordu: önceki değeri sakla, yeni değeri yaz, kaydı
tut. Aynı tekrar ücretlendirme tarafında daha büyük ölçekte var. Ücret hesabı altı adımdan
geçer — ücretli ağırlığı belirle, taban ücreti hesapla, bölge katsayısını uygula, ek hizmet
ücretini ekle, indirimi düş, asgari ücreti uygula — ve yurt içi, ekspres, yurt dışı taşıyıcıları
için üç dosya bu altı adımı ayrı ayrı sıralar. Adımların **sırası** her üçünde aynı olmak
zorundadır; değişen yalnızca birkaç adımın içeriğidir.

**Şablon yöntem** (template method) sırayı tek bir yere alır ve değişen adımları dışarıdan
gelen kancalara bırakır. Kazanç iki sayıyla ölçülür: yinelenen satır sayısı ve sıraya bir adım
eklendiğinde düzenlenen dosya sayısı. Bir üçüncü sayı da var ve o kalıbın asıl gerekçesi: sıra
bir kopyada saparsa üretilen tutar yanlış olur.

#### Yinelenen İskelet

Ortak veri ve bölge katsayıları iki sürümde paylaşılır.

```sh
mkdir -p yinelenen sablon
```

```js
// bolge.mjs — iki surumun paylastigi bolge katsayilari ve gonderi kumesi
export const BOLGE = { "1": 100, "2": 130, "3": 175 };
export const GONDERILER = [
  { kod: "GN-1", agirlik: 2, hacim: 8, bolge: "1", indirim: 0 },
  { kod: "GN-2", agirlik: 6, hacim: 60, bolge: "2", indirim: 10 },
  { kod: "GN-3", agirlik: 14, hacim: 20, bolge: "3", indirim: 0 },
  { kod: "GN-4", agirlik: 1, hacim: 3, bolge: "2", indirim: 40 },
  { kod: "GN-5", agirlik: 22, hacim: 180, bolge: "3", indirim: 25 },
];
```

Yurt içi taşıyıcının dosyası altı adımı sıralar; ek hizmet ücreti sıfır olduğu için o adım
hiç yazılmamış.

```js
// yinelenen/yurtici.mjs — alti adim bu dosyada siralanmis, ek hizmet adimi atlanmis
import { BOLGE } from "../bolge.mjs";

export const ASGARI = 3900;

export function ucretle(gonderi) {
  const agirlik = Math.ceil(gonderi.agirlik);
  let tutar = 2500 + 420 * agirlik;
  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);
  tutar -= Math.round((tutar * gonderi.indirim) / 100);
  return Math.max(tutar, ASGARI);
}
```

```js
// yinelenen/ekspres.mjs — ayni alti adim yeniden siralanmis
import { BOLGE } from "../bolge.mjs";

export const ASGARI = 6500;

export function ucretle(gonderi) {
  const agirlik = Math.ceil(gonderi.agirlik);
  let tutar = 4000 + 640 * agirlik;
  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);
  tutar += 1500;
  tutar -= Math.round((tutar * gonderi.indirim) / 100);
  return Math.max(tutar, ASGARI);
}
```

Üçüncü dosyada son iki adım ters sırada: asgari ücret indirimden **önce** uygulanmış.

```js
// yinelenen/yurtdisi.mjs — ayni alti adim, son iki adim ters sirada
import { BOLGE } from "../bolge.mjs";

export const ASGARI = 12000;

export function ucretle(gonderi) {
  const agirlik = Math.max(Math.ceil(gonderi.agirlik), Math.ceil(gonderi.hacim / 5));
  let tutar = 6000 + 900 * agirlik;
  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);
  tutar += 2200;
  tutar = Math.max(tutar, ASGARI);
  return tutar - Math.round((tutar * gonderi.indirim) / 100);
}
```

#### İskeletin Tek Yere Alınması

Şablon sürümünde sıra bir dosyadadır ve değişen dört şey kancadır: ücretli ağırlık, taban
ücret, ek hizmet ve asgari ücret.

```js
// sablon/sablon.mjs — degismeyen iskelet, alti adim tek yerde
import { BOLGE } from "../bolge.mjs";

export function ucretle(tarife, gonderi) {
  const agirlik = tarife.ucretliAgirlik(gonderi);
  let tutar = tarife.tabanUcret(agirlik);
  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);
  tutar += tarife.ekHizmet(gonderi);
  tutar -= Math.round((tutar * gonderi.indirim) / 100);
  return Math.max(tutar, tarife.asgari);
}
```

```js
// sablon/tarifeler.mjs — yalnizca degisen adimlar
const agirlikca = (g) => Math.ceil(g.agirlik);

export const yurtici = {
  ad: "yurtici",
  asgari: 3900,
  ucretliAgirlik: agirlikca,
  tabanUcret: (a) => 2500 + 420 * a,
  ekHizmet: () => 0,
};

export const ekspres = {
  ad: "ekspres",
  asgari: 6500,
  ucretliAgirlik: agirlikca,
  tabanUcret: (a) => 4000 + 640 * a,
  ekHizmet: () => 1500,
};

export const yurtdisi = {
  ad: "yurtdisi",
  asgari: 12000,
  ucretliAgirlik: (g) => Math.max(Math.ceil(g.agirlik), Math.ceil(g.hacim / 5)),
  tabanUcret: (a) => 6000 + 900 * a,
  ekHizmet: () => 2200,
};
```

Bu, strateji dersindeki tarife nesnesinden bir noktada ayrılır: strateji bütün hesabı
devralıyordu, kanca yalnız bir adımı devralır. Sıranın sahibi iskelettir; kanca ne zaman
çağrıldığını seçemez.

#### Sapmanın Ölçülmesi

Sürücü betik on beş hesabı iki sürümde de yapar, sapan sonuçları ve asgari ücretin altına
düşen tutarları sayar.

```js
// kosum.mjs — iki surumu ayni gonderilerle kosar, asgari altina dusen tutari sayar
import { GONDERILER } from "./bolge.mjs";
import * as yurtici from "./yinelenen/yurtici.mjs";
import * as ekspres from "./yinelenen/ekspres.mjs";
import * as yurtdisi from "./yinelenen/yurtdisi.mjs";
import { ucretle } from "./sablon/sablon.mjs";
import * as tarifeler from "./sablon/tarifeler.mjs";

const YINELENEN = { yurtici, ekspres, yurtdisi };
let sapma = 0;
let asgariAlti = { yinelenen: 0, sablon: 0 };

for (const ad of ["yurtici", "ekspres", "yurtdisi"]) {
  const satir = [];
  for (const g of GONDERILER) {
    const a = YINELENEN[ad].ucretle(g);
    const b = ucretle(tarifeler[ad], g);
    if (a !== b) sapma += 1;
    if (a < YINELENEN[ad].ASGARI) asgariAlti.yinelenen += 1;
    if (b < tarifeler[ad].asgari) asgariAlti.sablon += 1;
    satir.push(`${g.kod}=${a}/${b}`);
  }
  console.log(`${ad.padEnd(9)} ${satir.join(" ")}`);
}
console.log(`iki surum arasindaki sapma sayisi = ${sapma}`);
console.log(`asgari ucretin altina dusen tutar: yinelenen=${asgariAlti.yinelenen} sablon=${asgariAlti.sablon}`);
```

```
yurtici   GN-1=3900/3900 GN-2=5873/5873 GN-3=14665/14665 GN-4=3900/3900 GN-5=15409/15409
ekspres   GN-1=6780/6780 GN-2=10523/10523 GN-3=24180/24180 GN-4=6500/6500 GN-5=24855/24855
yurtdisi  GN-1=12000/12000 GN-2=21636/21636 GN-3=34750/34750 GN-4=7200/12000 GN-5=52050/52050
iki surum arasindaki sapma sayisi = 1
asgari ucretin altina dusen tutar: yinelenen=1 sablon=0
```

On beş hesabın on dördü aynı; biri sapıyor. Yüzde kırk indirimli GN-4, yurt dışı taşıyıcıda
yinelenen sürümde 7200, şablon sürümünde 12000. Asgari ücret 12000 olduğu için yinelenen
sürümün ürettiği tutar sözleşmeye aykırıdır. Sapmanın kaynağı bir formül hatası değil, adım
sırasıdır: iskelet üç kez yazıldığında üçüncüsünde iki adım yer değiştirmiştir. Şablon sürümünde
sıra kancalara açık olmadığı için bu sapmanın yazılabileceği bir yer yok.

#### Yinelenen Satır ve Yeni Adım

İkinci ölçü statiktir: her sürümde kaç anlamlı satır var ve bunların kaçı en az iki dosyada
birden geçiyor.

```js
// yineleme.mjs — iki surumde yinelenen satir sayisini sayar
import { readFileSync, readdirSync } from "node:fs";

const anlamli = (yol) =>
  readFileSync(yol, "utf8")
    .split("\n")
    .map((s) => s.trim())
    .filter((s) => s.length > 0 && s.startsWith("//") === false && s.startsWith("import ") === false);

function say(dizin) {
  const dosyalar = readdirSync(dizin).sort();
  const sayim = new Map();
  for (const d of dosyalar) {
    for (const s of new Set(anlamli(`${dizin}/${d}`))) sayim.set(s, (sayim.get(s) ?? 0) + 1);
  }
  const yinelenen = [...sayim.entries()].filter(([, n]) => n >= 2);
  const toplam = dosyalar.reduce((t, d) => t + anlamli(`${dizin}/${d}`).length, 0);
  return { dosya: dosyalar.length, toplam, yinelenen: yinelenen.length };
}

for (const dizin of ["yinelenen", "sablon"]) {
  const r = say(dizin);
  console.log(`${dizin.padEnd(10)} dosya=${r.dosya}  anlamli satir=${r.toplam}  en az iki dosyada gecen satir=${r.yinelenen}`);
}
```

```
yinelenen  dosya=3  anlamli satir=26  en az iki dosyada gecen satir=6
sablon     dosya=2  anlamli satir=30  en az iki dosyada gecen satir=0
```

Altıya karşı sıfır. Bu altı satır bilgi yinelemesidir: aynı adımın aynı biçimde üç kez
yazılması. Şimdi sıraya yedinci bir adım giriyor — bölge katsayısından sonra yüzde altı yakıt
ek ücreti.

```js
// adim.mjs — yedinci adimi (yakit ek ucreti) iki surume de ekler
import { cpSync, readFileSync, readdirSync, writeFileSync } from "node:fs";

cpSync("yinelenen", "yinelenen-yeni", { recursive: true });
cpSync("sablon", "sablon-yeni", { recursive: true });

const CAPA = "  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);";
const YAKIT = "  tutar += Math.round((tutar * 6) / 100);";

const ekle = (yol) => {
  const eski = readFileSync(yol, "utf8");
  const yeni = eski.replace(CAPA, `${CAPA}\n${YAKIT}`);
  writeFileSync(yol, yeni);
  return yeni === eski ? 0 : 1;
};

let yinelenenDuzenlenen = 0;
for (const d of readdirSync("yinelenen-yeni")) yinelenenDuzenlenen += ekle(`yinelenen-yeni/${d}`);
let sablonDuzenlenen = 0;
for (const d of readdirSync("sablon-yeni")) sablonDuzenlenen += ekle(`sablon-yeni/${d}`);

console.log(`yedinci adim icin duzenlenen dosya: yinelenen=${yinelenenDuzenlenen}  sablon=${sablonDuzenlenen}`);

const { GONDERILER } = await import("./bolge.mjs");
const y = await import("./yinelenen-yeni/yurtici.mjs");
const s = await import("./sablon-yeni/sablon.mjs");
const t = await import("./sablon-yeni/tarifeler.mjs");
console.log(`GN-2 yurtici yeni tutar: yinelenen=${y.ucretle(GONDERILER[1])}  sablon=${s.ucretle(t.yurtici, GONDERILER[1])}`);
```

```
yedinci adim icin duzenlenen dosya: yinelenen=3  sablon=1
GN-2 yurtici yeni tutar: yinelenen=6226  sablon=6226
```

Üç dosyaya karşı bir dosya ve iki sürüm yeni adımdan sonra da aynı tutarı veriyor. Üç dosyanın
düzenlenmesi yalnız iş yükü değil, üç ayrı sapma fırsatıdır; yukarıdaki 7200 tam olarak böyle
oluşmuştur.

#### Bedelin Sayımı

Aynı statik ölçüm bedeli de veriyor: anlamlı satır sayısı 26'dan 30'a **yükseldi**. Şablon
yöntem satır sayısını düşürmez; yinelemeyi düşürüp toplamı artırır, çünkü her kanca bir ad ve
bir imza ister. İkinci kalem iş yapmayan kancadır.

```sh
echo "bos adim (is yapmayan kanca) sayisi: yinelenen=$(grep -h 'tutar += 0;' yinelenen/*.mjs | wc -l | tr -d ' ')  sablon=$(grep -h 'ekHizmet: () => 0' sablon/*.mjs | wc -l | tr -d ' ')"
```

```
bos adim (is yapmayan kanca) sayisi: yinelenen=0  sablon=1
```

Yinelenen sürümde yurt içi taşıyıcı ek hizmet adımını hiç yazmamıştı; şablon sürümünde
yazmak zorunda, çünkü iskelet o kancayı çağırıyor. Sıfır döndüren bir kanca, kalıbın istediği
biçimsel bedeldir. Üçüncü kalem okuma maliyetidir: yinelenen sürümde bir taşıyıcının tam akışı
tek dosyada görünür, şablon sürümünde iskelet bir dosyada ve değişen adımlar başka dosyada
durur, yani akışı görmek için iki dosya okunur.

#### Özet

- Şablon yöntem değişmeyen adım sırasını tek bir gövdeye alır ve değişen adımları kancalara
  bırakır; kanca ne zaman çağrıldığını seçemez, sıranın sahibi iskelettir.
- En az iki dosyada birden geçen satır sayısı 6'dan 0'a indi; yedinci adım eklendiğinde
  düzenlenen dosya sayısı 3'ten 1'e indi ve iki sürüm aynı tutarı üretmeye devam etti.
- Sıranın bir kopyada sapması on beş hesaptan birinde yanlış tutar üretti: yüzde kırk indirimli
  gönderi yurt dışı taşıyıcıda asgari ücretin altına, 12000 yerine 7200'e düştü.
- Bedel: anlamlı satır sayısı 26'dan 30'a yükseldi ve iş yapmayan kanca sayısı 0'dan 1'e çıktı.
- Bir taşıyıcının tam ücret akışı yinelenen sürümde tek dosyada görünür, şablon sürümünde iki
  dosyaya dağılır.

#### Sonraki Adım

Şablonda sıra sabitti; hangi adımın çalışacağı baştan belliydi. Teslimatın kendisinde ise
hangi işlemin geçerli olduğu o anki duruma bağlı. Bir teslimat oluşturulmuş, toplanmış, yolda,
dağıtımda, teslim edilmiş ya da iade edilmiş olabilir; toplanmamış bir gönderi dağıtıma
çıkamaz, teslim edilmiş bir gönderi iade edilemez. Bu kurallar bugün alan üzerinde bayraklarla
ve iç içe koşullarla denetleniyor ve bazı geçersiz geçişler denetimden kaçıyor. Sonraki ders
tüm durum–olay çiftlerini tek tek deneyip kabul edilen geçersiz geçiş sayısını ve çevrimsel
karmaşıklığı ölçer, sonra durumu nesneye çevirip aynı sayıları yeniden hesaplar.

### Durum

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/durum

Teslimat yaşam döngüsünün beş bayrakla ve iç içe koşullarla tutulması ile her durumun kendi geçişlerini bilen nesnelere çevrilmesinin karşılaştırılması: otuz durum-olay çiftinde kabul edilen geçersiz geçiş sayısı, çevrimsel karmaşıklık, temsil edilebilir birleşim sayısı, yeni durum eklendiğinde değişen satır sayısı ve kalıbın bedeli olarak geçiş hedefi hatasının gecikmeli görünmesi.

Şablonda sıra sabitti; hangi adımın çalışacağı baştan belliydi. Teslimatın kendisinde ise
hangi işlemin geçerli olduğu o anki duruma bağlıdır. Bir teslimat oluşturulmuş, toplanmış,
yolda, dağıtımda, teslim edilmiş ya da iade edilmiş olabilir; toplanmamış bir gönderi dağıtıma
çıkamaz, teslim edilmiş bir gönderi iade edilemez. Bugün bu kurallar beş bayrakla ve iç içe
koşullarla denetleniyor.

Durum makinesi kavramı Yazılım Kalitesi ve Test müfredatında bir kez kurulmuştu: hata yaşam
döngüsü orada durum ve geçiş tablosuyla anlatılmış, geçişler test durumu üretmek için
kullanılmıştı. Burada yeni olan makinenin kendisi değil, **nesnelerle ifade edilişi**. **Durum**
(state) kalıbı her durumu bir nesne yapar ve o nesneye yalnızca kendi izin verdiği geçişleri
bildirir. Ölçülecek sayılar kabul edilen geçersiz geçiş sayısı, çevrimsel karmaşıklık ve
temsil edilebilir bayrak birleşimi sayısıdır.

#### Bayraklarla Tutulan Yaşam Döngüsü

Beş bayrak ve beş olay var. Her olay kendi koşulunu kendisi kurar; durumun adı bayrakların
sıralı denetiminden türetilir.

```sh
mkdir -p bayrak durum
```

```js
// bayrak/teslimat.mjs — durum bes bayrakta, gecisler ic ice kosullarda
export const teslimat = (kod) => {
  const d = { toplandi: false, yolda: false, dagitimda: false, teslim: false, iade: false };
  const durumAdi = () =>
    d.iade ? "iade" : d.teslim ? "teslim" : d.dagitimda ? "dagitimda" : d.yolda ? "yolda" : d.toplandi ? "toplandi" : "olusturuldu";
  return {
    kod,
    durumAdi,
    olay(ad) {
      if (ad === "topla") {
        if (d.toplandi === false && d.teslim === false) {
          d.toplandi = true;
          return true;
        }
        return false;
      }
      if (ad === "yolaCik") {
        if (d.toplandi && d.yolda === false) {
          d.yolda = true;
          return true;
        }
        return false;
      }
      if (ad === "dagitimaVer") {
        if (d.yolda && d.dagitimda === false) {
          d.dagitimda = true;
          return true;
        }
        return false;
      }
      if (ad === "teslimEt") {
        if (d.yolda && d.teslim === false && d.iade === false) {
          d.teslim = true;
          return true;
        }
        return false;
      }
      if (ad === "iadeEt") {
        if (d.yolda && d.iade === false) {
          d.iade = true;
          return true;
        }
        return false;
      }
      return false;
    },
  };
};
```

#### Durumun Nesneleştirilmesi

İkinci sürümde her durum bir nesnedir ve kendi geçiş kümesini taşır. Geçiş kümesi olay adından
sonraki durumun adına giden bir eşlemedir.

```js
// durum/durumlar.mjs — her durum yalnizca kendi gecislerini bilir
const durum = (ad, gecisler) => ({
  ad,
  izin: (olay) => Object.hasOwn(gecisler, olay),
  sonraki: (olay) => gecisler[olay],
});

export const DURUMLAR = {
  olusturuldu: durum("olusturuldu", { topla: "toplandi" }),
  toplandi: durum("toplandi", { yolaCik: "yolda" }),
  yolda: durum("yolda", { dagitimaVer: "dagitimda", iadeEt: "iade" }),
  dagitimda: durum("dagitimda", { teslimEt: "teslim", iadeEt: "iade" }),
  teslim: durum("teslim", {}),
  iade: durum("iade", {}),
};
```

```js
// durum/teslimat.mjs — gecis karari durum nesnesine sorulur
import { DURUMLAR } from "./durumlar.mjs";

export const teslimat = (kod) => {
  let su = DURUMLAR.olusturuldu;
  return {
    kod,
    durumAdi: () => su.ad,
    olay(ad) {
      if (su.izin(ad) === false) return false;
      su = DURUMLAR[su.sonraki(ad)];
      return true;
    },
  };
};
```

Teslimat nesnesi artık hangi olayın hangi durumda geçerli olduğunu bilmiyor; kararı elindeki
durum nesnesine soruyor. Bu, sorma–söyle ilkesinin durum makinesindeki karşılığıdır: bilgi
kararın verildiği yerdedir.

#### Otuz Çiftin Tek Tek Denenmesi

Ölçüm, altı durumun her birinde beş olayın da denenmesiyle yapılır: otuz çift. Beklenti tablosu
ve her duruma götüren yol ayrı bir dosyada durur.

```js
// tablo.mjs — gecerli gecis tablosu ve her duruma goturen yol
export const OLAYLAR = ["topla", "yolaCik", "dagitimaVer", "teslimEt", "iadeEt"];
export const YOL = {
  olusturuldu: [],
  toplandi: ["topla"],
  yolda: ["topla", "yolaCik"],
  dagitimda: ["topla", "yolaCik", "dagitimaVer"],
  teslim: ["topla", "yolaCik", "dagitimaVer", "teslimEt"],
  iade: ["topla", "yolaCik", "iadeEt"],
};
export const GECERLI = {
  olusturuldu: { topla: "toplandi" },
  toplandi: { yolaCik: "yolda" },
  yolda: { dagitimaVer: "dagitimda", iadeEt: "iade" },
  dagitimda: { teslimEt: "teslim", iadeEt: "iade" },
  teslim: {},
  iade: {},
};
```

```js
// kosum.mjs — otuz durum-olay ciftini iki surumde de dener
import { teslimat as bayrakTeslimat } from "./bayrak/teslimat.mjs";
import { teslimat as durumTeslimat } from "./durum/teslimat.mjs";
import { GECERLI, OLAYLAR, YOL } from "./tablo.mjs";

function dene(uret, etiket) {
  let kabulEdilenGecersiz = 0;
  let reddedilenGecerli = 0;
  const satirlar = [];
  for (const [durumAdi, yol] of Object.entries(YOL)) {
    const isaret = [];
    for (const olay of OLAYLAR) {
      const t = uret("TS-1");
      for (const o of yol) t.olay(o);
      const kabul = t.olay(olay);
      const gecerli = Object.hasOwn(GECERLI[durumAdi], olay);
      if (kabul && gecerli === false) kabulEdilenGecersiz += 1;
      if (kabul === false && gecerli) reddedilenGecerli += 1;
      isaret.push(kabul === gecerli ? "." : kabul ? "K" : "R");
    }
    satirlar.push(`  ${durumAdi.padEnd(12)} ${isaret.join(" ")}`);
  }
  console.log(`${etiket} (${OLAYLAR.join(" ")}):`);
  for (const s of satirlar) console.log(s);
  console.log(`  ${etiket.padEnd(12)} kabul edilen gecersiz=${kabulEdilenGecersiz}  reddedilen gecerli=${reddedilenGecerli}`);
}

dene(bayrakTeslimat, "bayrak");
dene(durumTeslimat, "durum");
```

```
bayrak (topla yolaCik dagitimaVer teslimEt iadeEt):
  olusturuldu  . . . . .
  toplandi     . . . . .
  yolda        . . . K .
  dagitimda    . . . . .
  teslim       . . . . K
  iade         . . K . .
  bayrak       kabul edilen gecersiz=3  reddedilen gecerli=0
durum (topla yolaCik dagitimaVer teslimEt iadeEt):
  olusturuldu  . . . . .
  toplandi     . . . . .
  yolda        . . . . .
  dagitimda    . . . . .
  teslim       . . . . .
  iade         . . . . .
  durum        kabul edilen gecersiz=0  reddedilen gecerli=0
```

Üçe karşı sıfır. Bayrak sürümü hiçbir geçerli geçişi reddetmiyor, yani kabaca bozuk değil;
fazla **izin veriyor**. Üç `K` işareti üç somut hatadır: dağıtıma çıkmamış bir gönderi teslim
edilebiliyor, teslim edilmiş bir gönderi iade edilebiliyor, iade edilmiş bir gönderi dağıtıma
verilebiliyor. Üçünün kaynağı aynı: her olay kendi koşulunu kendisi kuruyor ve hiçbiri
"şu anda hangi durumdayım" sorusunu tek bir yerden sormuyor.

#### Karmaşıklık ve Temsil Uzayı

```js
// karmasiklik.mjs — karar noktasi sayarak cevrimsel karmasiklik verir
import { readFileSync, readdirSync } from "node:fs";

const KARAR = /\bif\b|&&|\|\||\?|\bcase\b|\bwhile\b|\bfor\b/g;

for (const dizin of ["bayrak", "durum"]) {
  let karar = 0;
  let satir = 0;
  const dosyalar = readdirSync(dizin).sort();
  for (const d of dosyalar) {
    const metin = readFileSync(`${dizin}/${d}`, "utf8").replace(/^\/\/.*$/gm, "");
    karar += (metin.match(KARAR) ?? []).length;
    satir += metin.split("\n").filter((s) => s.trim().length > 0).length;
  }
  console.log(`${dizin.padEnd(7)} dosya=${dosyalar.length}  satir=${satir}  karar noktasi=${karar}  cevrimsel karmasiklik=${karar + 1}`);
}
```

```
bayrak  dosya=1  satir=47  karar noktasi=21  cevrimsel karmasiklik=22
durum   dosya=2  satir=26  karar noktasi=1  cevrimsel karmasiklik=2
```

Yirmi ikiye karşı iki. Fark, kararların koddan veriye taşınmasından gelir: geçiş kuralları
artık dallanma değil, eşleme girdisidir. İkinci sayı bunun neden hataya yer bırakmadığını
gösterir.

```js
// bedel.mjs — temsil uzayi ve gecis hedefi yanlis yazildiginda ortaya cikan gecikme
import { cpSync, readFileSync, writeFileSync } from "node:fs";
import { DURUMLAR } from "./durum/durumlar.mjs";

const BAYRAK = 5;
console.log(`bayrak: temsil edilebilir birlesim=${2 ** BAYRAK}  gecerli durum=${Object.keys(DURUMLAR).length}`);
console.log(`durum : temsil edilebilir birlesim=${Object.keys(DURUMLAR).length}  gecerli durum=${Object.keys(DURUMLAR).length}`);

cpSync("durum", "durum-hatali", { recursive: true });
writeFileSync(
  "durum-hatali/durumlar.mjs",
  readFileSync("durum/durumlar.mjs", "utf8").replace('dagitimaVer: "dagitimda"', 'dagitimaVer: "dagitimdaa"'),
);

const t = (await import("./durum-hatali/teslimat.mjs")).teslimat("TS-3");
let olayNo = 0;
for (const o of ["topla", "yolaCik", "dagitimaVer", "teslimEt"]) {
  olayNo += 1;
  try {
    const kabul = t.olay(o);
    console.log(`olay ${olayNo} ${o.padEnd(12)} kabul=${kabul ? 1 : 0}`);
  } catch (e) {
    console.log(`olay ${olayNo} ${o.padEnd(12)} ${e.constructor.name} firlatildi`);
    break;
  }
}
```

```
bayrak: temsil edilebilir birlesim=32  gecerli durum=6
durum : temsil edilebilir birlesim=6  gecerli durum=6
olay 1 topla        kabul=1
olay 2 yolaCik      kabul=1
olay 3 dagitimaVer  kabul=1
olay 4 teslimEt     TypeError firlatildi
```

Otuz ikiye karşı altı. Beş bağımsız bayrak otuz iki birleşim üretir ve bunların yirmi altısı
hiçbir gerçek duruma karşılık gelmez; yolda olmadan dağıtımda olmak ya da hem teslim hem iade
olmak temsil edilebilir. Durum nesnesi tek bir değişken tuttuğu için temsil uzayı geçerli
durum kümesine eşittir.

Çıktının son dört satırı bedelin ilk kalemidir. Geçiş hedefi bir harf yanlış yazıldığında
`dagitimaVer` olayı **kabul edilir**, hata ancak bir sonraki olayda ortaya çıkar. Geçişler veri
olduğu için hedef adları kod değildir ve yanlış ad yazıldığı yerde değil, kullanıldığı yerde
patlar. Bedelin ikinci kalemi dosya sayısıdır: bir dosyaya karşı iki dosya ve bir olayın kabul
edilip edilmediğini anlamak için ikisini de okumak gerekir.

#### Yeni Durum Eklendiğinde

Adres teyidi bekleyen gönderiler için `beklemede` durumu ve iki yeni olay isteniyor:
yoldaki gönderi `beklet` ile bekletiliyor, `devamEt` ile yeniden yola çıkıyor. Betik değişikliği
iki sürüme de uygular, değişen satırları sayar ve aynı olay dizisini ikisinde de koşar.

```js
// yenidurum.mjs — "beklemede" durumunu iki surume de ekler ve degisimi olcer
import { cpSync, readFileSync, writeFileSync } from "node:fs";

cpSync("bayrak", "bayrak-yeni", { recursive: true });
cpSync("durum", "durum-yeni", { recursive: true });

const degistir = (yol, arayan, yeni) => {
  const eski = readFileSync(yol, "utf8");
  writeFileSync(yol, eski.replace(arayan, yeni));
};

degistir(
  "bayrak-yeni/teslimat.mjs",
  "  const d = { toplandi: false, yolda: false, dagitimda: false, teslim: false, iade: false };",
  "  const d = { toplandi: false, yolda: false, dagitimda: false, teslim: false, iade: false, beklemede: false };",
);
degistir(
  "bayrak-yeni/teslimat.mjs",
  "    d.iade ? \"iade\" : d.teslim ? \"teslim\" : d.dagitimda ? \"dagitimda\" : d.yolda ? \"yolda\" : d.toplandi ? \"toplandi\" : \"olusturuldu\";",
  "    d.iade ? \"iade\" : d.teslim ? \"teslim\" : d.beklemede ? \"beklemede\" : d.dagitimda ? \"dagitimda\" : d.yolda ? \"yolda\" : d.toplandi ? \"toplandi\" : \"olusturuldu\";",
);
degistir(
  "bayrak-yeni/teslimat.mjs",
  '      if (ad === "dagitimaVer") {\n        if (d.yolda && d.dagitimda === false) {',
  `      if (ad === "beklet") {
        if (d.yolda && d.beklemede === false && d.dagitimda === false) {
          d.beklemede = true;
          return true;
        }
        return false;
      }
      if (ad === "devamEt") {
        if (d.beklemede) {
          d.beklemede = false;
          return true;
        }
        return false;
      }
      if (ad === "dagitimaVer") {
        if (d.yolda && d.dagitimda === false && d.beklemede === false) {`,
);

degistir(
  "durum-yeni/durumlar.mjs",
  '  yolda: durum("yolda", { dagitimaVer: "dagitimda", iadeEt: "iade" }),',
  `  yolda: durum("yolda", { dagitimaVer: "dagitimda", iadeEt: "iade", beklet: "beklemede" }),
  beklemede: durum("beklemede", { devamEt: "yolda", iadeEt: "iade" }),`,
);

const fark = (a, b) => {
  const x = readFileSync(a, "utf8").split("\n");
  const y = readFileSync(b, "utf8").split("\n");
  const kume = new Set(x.map((s) => s.trim()));
  return y.filter((s) => s.trim().length > 0 && kume.has(s.trim()) === false).length;
};

console.log(`bayrak: eklenen/degisen satir=${fark("bayrak/teslimat.mjs", "bayrak-yeni/teslimat.mjs")}`);
console.log(`durum : eklenen/degisen satir=${fark("durum/durumlar.mjs", "durum-yeni/durumlar.mjs")}`);

const b = (await import("./bayrak-yeni/teslimat.mjs")).teslimat("TS-2");
const c = (await import("./durum-yeni/teslimat.mjs")).teslimat("TS-2");
for (const t of [b, c]) {
  const iz = [];
  for (const o of ["topla", "yolaCik", "beklet", "dagitimaVer", "devamEt", "dagitimaVer", "teslimEt"]) {
    iz.push(`${o}=${t.olay(o) ? "1" : "0"}`);
  }
  console.log(`${t.durumAdi().padEnd(10)} ${iz.join(" ")}`);
}
```

```
bayrak: eklenen/degisen satir=9
durum : eklenen/degisen satir=2
teslim     topla=1 yolaCik=1 beklet=1 dagitimaVer=0 devamEt=1 dagitimaVer=1 teslimEt=1
teslim     topla=1 yolaCik=1 beklet=1 dagitimaVer=0 devamEt=1 dagitimaVer=1 teslimEt=1
```

Dokuz satıra karşı iki satır ve iki sürüm aynı olay dizisinde aynı kabul dizisini ve aynı son
durumu veriyor. Dokuz satırın bir kısmı var olan bir koşulun içine giriyor — `dagitimaVer`
koşuluna üçüncü bir denetim eklendi — yani yeni durum eski geçişlerin davranışını da riske
atıyor. Durum sürümünde eski geçiş satırlarına dokunulmadı.

#### Özet

- Durum kalıbı her durumu kendi geçiş kümesini bilen bir nesne yapar; teslimat nesnesi
  hangi olayın geçerli olduğunu bilmez, elindeki durum nesnesine sorar.
- Otuz durum-olay çiftinin denenmesinde bayrak sürümü 3 geçersiz geçişi kabul etti, durum
  sürümü 0; iki sürüm de hiçbir geçerli geçişi reddetmedi.
- Çevrimsel karmaşıklık 22'den 2'ye indi; temsil edilebilir birleşim sayısı 32'den 6'ya indi,
  yani 26 anlamsız bayrak birleşimi ortadan kalktı.
- Yeni bir durum ve iki olay eklendiğinde bayrak sürümünde 9, durum sürümünde 2 satır değişti;
  iki sürüm aynı olay dizisinde aynı sonucu verdi.
- Bedel: dosya sayısı 1'den 2'ye çıktı ve geçiş hedefi yanlış yazıldığında hata yazıldığı
  olayda değil bir sonraki olayda ortaya çıktı.

#### Sonraki Adım

Durum nesnesi bir soruyu tek bir yerden yanıtlıyor: bu olay burada geçerli mi. Kitaplığın
ücret düzeltmeleri tarafında ise soru tek bir yere sorulamıyor. Bir gönderiye sözleşmeli
müşteri indirimi, hacim indirimi, kampanya indirimi ve elle girilen düzeltme sırayla
uygulanabilir; kuralların bir kısmı gönderiye uymaz ve atlanır, bir kısmı uygulandıktan sonra
zinciri keser. Bugün bu kurallar tek gövdede iç içe koşullarla duruyor ve sıraları gövdenin
içine gömülü. Sonraki ders bu gövdenin karar noktalarını sayar, kural sırası değiştiğinde
düzenlenen satırları ölçer, sonra isteği işleyiciler arasında aktaran bir zincir kurup aynı
sayıları yeniden hesaplar.

### Sorumluluk Zinciri

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/sorumluluk-zinciri

İndirim kurallarının tek gövdede sıralanması ile isteğin işleyiciler arasında aktarılmasının karşılaştırılması: kural sırası değiştiğinde değişen satır sayısı, üç yeni kural eklendiğinde en karmaşık gövdenin çevrimsel karmaşıklığı ile dosya sayısının nasıl büyüdüğü ve kalıbın bedeli olarak hiçbir kurala uymayan isteğin ayırt edilemeyişi.

Durum nesnesi bir soruyu tek bir yerden yanıtlıyordu: bu olay burada geçerli mi. Ücret
düzeltmeleri tarafında soru tek bir yere sorulamıyor. Bir gönderiye sözleşmeli müşteri indirimi,
hacim indirimi, kampanya indirimi ve elle girilen düzeltme sırayla uygulanabilir; kuralların bir
kısmı gönderiye uymaz ve atlanır, bir kısmı uygulandıktan sonra zinciri keser. Bugün bu kurallar
tek gövdede iç içe koşullarla duruyor ve sıraları gövdenin akışına gömülü.

**Sorumluluk zinciri** (chain of responsibility) isteği bir işleyici dizisine verir. Her işleyici
isteğe uygun olup olmadığına kendisi karar verir, uygunsa işini yapar, ardından isteği sonraki
işleyiciye bırakır ya da zinciri keser. Ölçülecek sayılar kural sırası değiştiğinde değişen satır
sayısı, kural sayısı arttığında en karmaşık gövdenin çevrimsel karmaşıklığı ve dosya sayısıdır.

#### Dört Kural

Kurallar sırayla şunlar: sözleşmeli müşteriye yüzde on iki, yirmi kilogramdan ağır gönderiye
yüzde sekiz, üçüncü bölgedeki hafif gönderiye yüzde on beş (uygulanınca zinciri keser), elle
girilen orana eşit düzeltme (uygulanınca zinciri keser).

```sh
mkdir -p gomulu zincir

cat > zincir/sozlesmeli.mjs <<'SON'
// zincir/sozlesmeli.mjs
export const sozlesmeli = {
  ad: "sozlesmeli",
  keser: false,
  uygunMu: (g) => g.sozlesmeli === true,
  oran: () => 12,
};
SON

cat > zincir/hacim.mjs <<'SON'
// zincir/hacim.mjs
export const hacim = {
  ad: "hacim",
  keser: false,
  uygunMu: (g) => g.agirlik >= 20,
  oran: () => 8,
};
SON

cat > zincir/kampanya.mjs <<'SON'
// zincir/kampanya.mjs — uygulandiginda zinciri keser
export const kampanya = {
  ad: "kampanya",
  keser: true,
  uygunMu: (g) => g.bolge === "3" && g.agirlik < 5,
  oran: () => 15,
};
SON

cat > zincir/elle.mjs <<'SON'
// zincir/elle.mjs — uygulandiginda zinciri keser
export const elle = {
  ad: "elle",
  keser: true,
  uygunMu: (g) => typeof g.elleOran === "number",
  oran: (g) => g.elleOran,
};
SON
```

```js
// veri.mjs — indirim kurallarinin denenecegi gonderiler
export const GONDERILER = [
  { kod: "GN-1", agirlik: 2, bolge: "1", sozlesmeli: false },
  { kod: "GN-2", agirlik: 6, bolge: "2", sozlesmeli: true },
  { kod: "GN-3", agirlik: 22, bolge: "3", sozlesmeli: false },
  { kod: "GN-4", agirlik: 3, bolge: "3", sozlesmeli: true },
  { kod: "GN-5", agirlik: 25, bolge: "2", sozlesmeli: false, elleOran: 5 },
];
```

Birinci sürümde dört kural ve sıraları tek gövdededir. Gövde ayrıca kaç kuralın yoklandığını
sayar; bu sayı iki sürümü karşılaştırmayı sağlar.

```js
// gomulu/indirim.mjs — dort kural ve siralari tek govdede
export function indirim(gonderi) {
  const uygulanan = [];
  let oran = 0;
  let ziyaret = 1;
  if (gonderi.sozlesmeli === true) {
    oran += 12;
    uygulanan.push("sozlesmeli");
  }
  ziyaret += 1;
  if (gonderi.agirlik >= 20) {
    oran += 8;
    uygulanan.push("hacim");
  }
  ziyaret += 1;
  if (gonderi.bolge === "3" && gonderi.agirlik < 5) {
    oran += 15;
    uygulanan.push("kampanya");
    return { oran, ziyaret, uygulanan };
  }
  ziyaret += 1;
  if (typeof gonderi.elleOran === "number") {
    oran += gonderi.elleOran;
    uygulanan.push("elle");
    return { oran, ziyaret, uygulanan };
  }
  return { oran, ziyaret, uygulanan };
}
```

İkinci sürümde sıra bir dizidir ve tek satırda durur.

```js
// zincir/sira.mjs — zincirin sirasi tek satirda
import { sozlesmeli } from "./sozlesmeli.mjs";
import { hacim } from "./hacim.mjs";
import { kampanya } from "./kampanya.mjs";
import { elle } from "./elle.mjs";

export const SIRA = [sozlesmeli, hacim, kampanya, elle];
```

```js
// zincir/zincir.mjs — istek kesilene ya da zincir bitene kadar aktarilir
export function indirim(kurallar, gonderi) {
  const uygulanan = [];
  let oran = 0;
  let ziyaret = 0;
  for (const kural of kurallar) {
    ziyaret += 1;
    if (kural.uygunMu(gonderi) === false) continue;
    oran += kural.oran(gonderi);
    uygulanan.push(kural.ad);
    if (kural.keser) break;
  }
  return { oran, ziyaret, uygulanan };
}
```

Zincir yürütücüsü hiçbir kuralın ne yaptığını bilmez; yalnızca `uygunMu`, `oran` ve `keser`
üçlüsünü kullanır. Kural sayısı ne olursa olsun bu gövde değişmez.

#### Davranışın Eşitliği

```js
// kosum.mjs — iki surumu ayni gonderilerle kosar ve sapmayi sayar
import { GONDERILER } from "./veri.mjs";
import { indirim as gomuluIndirim } from "./gomulu/indirim.mjs";
import { indirim as zincirIndirim } from "./zincir/zincir.mjs";
import { SIRA } from "./zincir/sira.mjs";

let sapma = 0;
for (const g of GONDERILER) {
  const a = gomuluIndirim(g);
  const b = zincirIndirim(SIRA, g);
  if (a.oran !== b.oran || a.uygulanan.join() !== b.uygulanan.join()) sapma += 1;
  console.log(
    `${g.kod} gomulu oran=${a.oran} ziyaret=${a.ziyaret} [${a.uygulanan.join(" ")}]  zincir oran=${b.oran} ziyaret=${b.ziyaret} [${b.uygulanan.join(" ")}]`,
  );
}
console.log(`iki surum arasindaki sapma sayisi = ${sapma}`);
```

```
GN-1 gomulu oran=0 ziyaret=4 []  zincir oran=0 ziyaret=4 []
GN-2 gomulu oran=12 ziyaret=4 [sozlesmeli]  zincir oran=12 ziyaret=4 [sozlesmeli]
GN-3 gomulu oran=8 ziyaret=4 [hacim]  zincir oran=8 ziyaret=4 [hacim]
GN-4 gomulu oran=27 ziyaret=3 [sozlesmeli kampanya]  zincir oran=27 ziyaret=3 [sozlesmeli kampanya]
GN-5 gomulu oran=13 ziyaret=4 [hacim elle]  zincir oran=13 ziyaret=4 [hacim elle]
iki surum arasindaki sapma sayisi = 0
```

Sapma sıfır: oranlar, uygulanan kural listeleri ve ziyaret sayıları birebir aynı. GN-4'te ziyaret
sayısının üç olması kesme davranışının çalıştığını gösteriyor; kampanya uygulandıktan sonra elle
düzeltme yoklanmadı.

#### Sıra Değiştiğinde

Elle girilen düzeltmenin her şeyin önüne geçmesi isteniyor: operatörün girdiği oran diğer
indirimleri bastırmalı. Gömülü sürümde bu, bir bloğun gövde içinde taşınmasıdır; zincir sürümünde
dizinin sırasıdır.

```sh
cp -r gomulu gomulu-yeni
cp -r zincir zincir-yeni

cat > gomulu-yeni/indirim.mjs <<'SON'
// gomulu/indirim.mjs — dort kural ve siralari tek govdede
export function indirim(gonderi) {
  const uygulanan = [];
  let oran = 0;
  let ziyaret = 1;
  if (typeof gonderi.elleOran === "number") {
    oran += gonderi.elleOran;
    uygulanan.push("elle");
    return { oran, ziyaret, uygulanan };
  }
  ziyaret += 1;
  if (gonderi.sozlesmeli === true) {
    oran += 12;
    uygulanan.push("sozlesmeli");
  }
  ziyaret += 1;
  if (gonderi.agirlik >= 20) {
    oran += 8;
    uygulanan.push("hacim");
  }
  ziyaret += 1;
  if (gonderi.bolge === "3" && gonderi.agirlik < 5) {
    oran += 15;
    uygulanan.push("kampanya");
    return { oran, ziyaret, uygulanan };
  }
  return { oran, ziyaret, uygulanan };
}
SON

sed -i.y 's/\[sozlesmeli, hacim, kampanya, elle\]/[elle, sozlesmeli, hacim, kampanya]/' zincir-yeni/sira.mjs
rm -f zincir-yeni/*.y

for k in gomulu zincir; do
  echo "$k: duzenlenen dosya=$(diff -rq $k $k-yeni | grep -c '^Files')  degisen satir=$(diff -r -u $k $k-yeni | grep '^[+-][^+-]' | grep -vc '^[+-]//')"
done

cat > kosum-yeni.mjs <<'SON'
import { GONDERILER } from "./veri.mjs";
import { indirim as gomuluIndirim } from "./gomulu-yeni/indirim.mjs";
import { indirim as zincirIndirim } from "./zincir-yeni/zincir.mjs";
import { SIRA } from "./zincir-yeni/sira.mjs";

let sapma = 0;
for (const g of GONDERILER) {
  const a = gomuluIndirim(g);
  const b = zincirIndirim(SIRA, g);
  if (a.oran !== b.oran || a.uygulanan.join() !== b.uygulanan.join()) sapma += 1;
}
const g5 = GONDERILER[4];
console.log(`GN-5 yeni sirada: gomulu oran=${gomuluIndirim(g5).oran}  zincir oran=${zincirIndirim(SIRA, g5).oran}`);
console.log(`yeni sirada iki surum arasindaki sapma sayisi = ${sapma}`);
SON
node kosum-yeni.mjs
```

```
gomulu: duzenlenen dosya=1  degisen satir=12
zincir: duzenlenen dosya=1  degisen satir=2
GN-5 yeni sirada: gomulu oran=5  zincir oran=5
yeni sirada iki surum arasindaki sapma sayisi = 0
```

On iki satıra karşı iki satır. GN-5'in oranı 13'ten 5'e düştü: elle düzeltme öne geçtiği için
hacim indirimi hiç yoklanmadı. Bu, sıranın bir biçim tercihi değil davranışın parçası olduğunu
gösteriyor — ve bu yüzden sıranın nerede yazıldığı önemlidir. Gömülü sürümde sıra, koşulların
gövde içindeki yerleşimidir; okumak için gövdenin tamamını izlemek gerekir. Zincir sürümünde sıra
tek satırlık bir dizidir.

#### Kural Sayısı Büyüdüğünde

Kalıbın karşılığını verip vermediği, kural sayısı arttığında görülür. Üç kural daha ekleniyor:
on kilogramdan ağır gönderiye yüzde üç yakıt indirimi, ilk gönderisini yapan müşteriye yüzde on,
üçüncü bölgeye yüzde dört.

```js
// buyume.mjs — uc yeni kural eklenince govde karmasikligi nasil buyuyor
import { cpSync, readFileSync, readdirSync, writeFileSync } from "node:fs";

const KARAR = /\bif\b|&&|\|\||\?|\bcase\b|\bwhile\b|\bfor\b|\bcontinue\b|\bbreak\b/g;
const enKarmasik = (dizin) =>
  Math.max(
    ...readdirSync(dizin).map((d) => (readFileSync(`${dizin}/${d}`, "utf8").replace(/^\/\/.*$/gm, "").match(KARAR) ?? []).length + 1),
  );

const once = { gomulu: enKarmasik("gomulu"), zincir: enKarmasik("zincir") };

cpSync("gomulu", "gomulu-uc", { recursive: true });
cpSync("zincir", "zincir-uc", { recursive: true });

const EK = `  ziyaret += 1;
  if (gonderi.agirlik >= 10) {
    oran += 3;
    uygulanan.push("yakit");
  }
  ziyaret += 1;
  if (gonderi.ilkMi === true) {
    oran += 10;
    uygulanan.push("ilkGonderi");
  }
  ziyaret += 1;
  if (gonderi.bolge === "3") {
    oran += 4;
    uygulanan.push("uzakBolge");
  }
  ziyaret += 1;`;
writeFileSync(
  "gomulu-uc/indirim.mjs",
  readFileSync("gomulu/indirim.mjs", "utf8").replace('  ziyaret += 1;\n  if (gonderi.bolge === "3" && gonderi.agirlik < 5) {', `${EK}\n  if (gonderi.bolge === "3" && gonderi.agirlik < 5) {`),
);

const YENI = [
  ["yakit", "(g) => g.agirlik >= 10", "() => 3"],
  ["ilk-gonderi", "(g) => g.ilkMi === true", "() => 10"],
  ["uzak-bolge", '(g) => g.bolge === "3"', "() => 4"],
];
for (const [ad, kosul, oran] of YENI) {
  const degisken = ad.replace(/-(\w)/g, (_, c) => c.toUpperCase());
  writeFileSync(
    `zincir-uc/${ad}.mjs`,
    `// zincir/${ad}.mjs\nexport const ${degisken} = {\n  ad: "${degisken}",\n  keser: false,\n  uygunMu: ${kosul},\n  oran: ${oran},\n};\n`,
  );
}
writeFileSync(
  "zincir-uc/sira.mjs",
  `${readFileSync("zincir/sira.mjs", "utf8")
    .replace('import { elle } from "./elle.mjs";', 'import { elle } from "./elle.mjs";\nimport { yakit } from "./yakit.mjs";\nimport { ilkGonderi } from "./ilk-gonderi.mjs";\nimport { uzakBolge } from "./uzak-bolge.mjs";')
    .replace("[sozlesmeli, hacim, kampanya, elle]", "[sozlesmeli, hacim, yakit, ilkGonderi, uzakBolge, kampanya, elle]")}`,
);

const sonra = { gomulu: enKarmasik("gomulu-uc"), zincir: enKarmasik("zincir-uc") };
console.log(`en karmasik govde  gomulu: ${once.gomulu} -> ${sonra.gomulu}   zincir: ${once.zincir} -> ${sonra.zincir}`);
console.log(`dosya sayisi       gomulu: ${readdirSync("gomulu").length} -> ${readdirSync("gomulu-uc").length}   zincir: ${readdirSync("zincir").length} -> ${readdirSync("zincir-uc").length}`);

const { GONDERILER } = await import("./veri.mjs");
const g = await import("./gomulu-uc/indirim.mjs");
const z = await import("./zincir-uc/zincir.mjs");
const s = await import("./zincir-uc/sira.mjs");
let sapma = 0;
let dusenIstek = 0;
for (const gonderi of GONDERILER) {
  const a = g.indirim(gonderi);
  const b = z.indirim(s.SIRA, gonderi);
  if (a.oran !== b.oran) sapma += 1;
  if (b.uygulanan.length === 0) dusenIstek += 1;
}
console.log(`yedi kuralda sapma=${sapma}  hicbir kurala uymayan gonderi=${dusenIstek}`);
```

```
en karmasik govde  gomulu: 6 -> 9   zincir: 6 -> 6
dosya sayisi       gomulu: 1 -> 1   zincir: 6 -> 9
yedi kuralda sapma=0  hicbir kurala uymayan gonderi=1
```

Dört kuralda kalıp karmaşıklık kazancı vermiyordu: altıya karşı altı. Zincir yürütücüsünün
kendisi, dört kuralı sıralayan gövde kadar karmaşıktır. Yedi kuralda ise yollar ayrılıyor.
Gömülü gövde 6'dan 9'a çıkıyor, zincir yürütücüsü 6'da kalıyor çünkü her yeni kural gövdeye
değil kendi dosyasına yazılıyor. Bedelin karşılığı da aynı satırda: dosya sayısı 6'dan 9'a
çıkıyor, gömülü sürümde 1'de kalıyor.

Kalıbın eşiği bu iki eğrinin kesiştiği yerdir ve buradaki ölçüm eşiği dört kural civarına
koyuyor: dördün altında zincir yalnızca dosya ekler.

#### Bedelin İkinci Kalemi

Son satırın son sayısı ayrı bir bedeldir: bir gönderi hiçbir kurala uymadı. Zincir bittiğinde
oran sıfırdır, ama "indirim yok" ile "hiçbir kural isteği ele almadı" aynı sonuçla temsil edilir.
Gömülü gövdede de aynı belirsizlik var, fakat zincirde bu sessizlik daha kolay gözden kaçar:
işleyicilerden biri yanlışlıkla listeden çıkarıldığında sonuç hâlâ geçerli görünür, yalnızca
oran düşer. Zincirin sonuna hiçbir zaman uygulanmayan bir işleyici koymak bu belirsizliği
kapatmanın bilinen yoludur; bu, konunun dokuzuncu dersinde ölçülecek boş nesne kalıbıdır.

#### Özet

- Sorumluluk zinciri isteği bir işleyici dizisine verir; her işleyici uygunluğuna kendi karar
  verir, işini yapar ve isteği sonrakine bırakır ya da zinciri keser.
- Dört kuralla iki sürüm birebir aynı oran, aynı uygulanan kural listesi ve aynı ziyaret
  sayısını üretti; sapma 0 çıktı.
- Kural sırası değiştiğinde gömülü sürümde 12, zincir sürümünde 2 satır değişti; sıra davranışın
  parçasıdır — elle düzeltme öne geçince GN-5'in oranı 13'ten 5'e indi.
- Dört kuralda en karmaşık gövde ikisinde de 6; yedi kuralda gömülü sürüm 9'a çıktı, zincir 6'da
  kaldı, karşılığında dosya sayısı 6'dan 9'a yükseldi.
- Bedel: kural başına bir dosya ve hiçbir işleyicinin ele almadığı istek ile indirimsiz istek
  aynı sonuçla temsil edilir.

#### Sonraki Adım

Zincir isteği bir dizide sırayla dolaştırıyor: dolaşma düz, tek yönlü ve sıra dizinin kendisinde.
Kitaplığın rota tarafında dolaşılacak yapı düz değil. Bir rota, aktarma noktalarından oluşan bir
ağaçtır: merkez, bölge deposu, dağıtım şubesi, teslim noktası. Bu ağaç üzerinde birbirinden
bağımsız beş işlem yapılıyor — toplam mesafe, en uzun bekleme, kapasite denetimi, etiket
listesi, maliyet toplamı — ve her işlem ağacı kendi gezinme koduyla dolaşıyor. Sonraki ders
gezinme kodunun kaç kez yinelendiğini sayar, sonra gezinmeyi işlemden ayırıp yeni işlem ve yeni
düğüm türü eklendiğinde dokunulan dosya sayısını iki yönde birden ölçer.

### Yineleyici ve Ziyaretçi

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/yineleyici-ve-ziyaretci

Rota ağacındaki beş işlemin her birinin kendi gezinme kodunu taşıması ile gezinmenin yineleyiciye, tür ayrımının ziyaretçiye alınmasının karşılaştırılması: gezinme kodu taşıyan yer sayısı, gezinme sırası değiştiğinde düzenlenen dosya sayısı, yeni düğüm türü eklendiğinde sessizce yanlış sonuç veren işlem sayısı ile hatayı bildiren işlem sayısı.

Zincir isteği bir dizide sırayla dolaştırıyordu: dolaşma düz, tek yönlü ve sıra dizinin
kendisindeydi. Rota tarafında dolaşılacak yapı düz değil. Bir rota, aktarma noktalarından oluşan
bir ağaçtır: merkez, bölge deposu, dağıtım şubesi, teslim noktası. Bu ağaç üzerinde birbirinden
bağımsız beş işlem yapılıyor — toplam mesafe, en uzun bekleme, kapasite denetimi, etiket listesi,
maliyet toplamı — ve her işlem ağacı kendi gezinme koduyla dolaşıyor.

İki kalıp bu durumun iki ayrı yarısını hedefler. **Yineleyici** (iterator) gezinmeyi işlemden
ayırır: ağacın nasıl dolaşıldığı tek yerde tanımlanır, işlemler yalnızca gelen düğümlerle
uğraşır. **Ziyaretçi** (visitor) düğüm türüne göre değişen davranışı işlemden ayırır: her işlem
her tür için bir yöntem taşır ve türün seçimi tek bir yerde yapılır. Ölçülecek sayılar gezinme
kodu taşıyan yer sayısı, gezinme sırası değiştiğinde düzenlenen dosya sayısı ve yeni bir düğüm
türü eklendiğinde ortaya çıkan hata sayısıdır.

#### Ağaç ve Beş İşlem

```sh
mkdir -p gezinme yineleyici ziyaretci
```

```js
// agac.mjs — rota agaci: merkez, bolge depolari, dagitim subeleri, teslim noktalari
const d = (tur, ad, mesafe, bekleme, kapasite, alt = []) => ({ tur, ad, mesafe, bekleme, kapasite, alt });

export const ROTA = d("merkez", "MRK", 0, 0, 5000, [
  d("depo", "DPO-A", 120, 4, 1800, [
    d("sube", "SB-A1", 35, 2, 400, [d("nokta", "NK-A1a", 6, 1, 0), d("nokta", "NK-A1b", 9, 3, 0)]),
    d("sube", "SB-A2", 48, 5, 900, [d("nokta", "NK-A2a", 4, 2, 0)]),
  ]),
  d("depo", "DPO-B", 260, 7, 300, [d("sube", "SB-B1", 52, 6, 650, [d("nokta", "NK-B1a", 11, 4, 0)])]),
]);
```

Birinci sürümde beş işlem beş ayrı özyineleme taşır. Her gövde hem ne hesaplayacağını hem ağacı
nasıl dolaşacağını bilir.

```js
// gezinme/islemler.mjs — bes islem, bes ayri gezinme
export function toplamMesafe(d) {
  let t = d.mesafe;
  for (const c of d.alt) t += toplamMesafe(c);
  return t;
}

export function enUzunBekleme(d) {
  let e = d.bekleme;
  for (const c of d.alt) e = Math.max(e, enUzunBekleme(c));
  return e;
}

export function darKapasite(d) {
  let n = d.tur !== "nokta" && d.kapasite < 500 ? 1 : 0;
  for (const c of d.alt) n += darKapasite(c);
  return n;
}

export function etiketler(d) {
  const bas = d.tur === "merkez" ? "M" : d.tur === "depo" ? "D" : d.tur === "sube" ? "S" : "N";
  let liste = [`${bas}:${d.ad}`];
  for (const c of d.alt) liste = liste.concat(etiketler(c));
  return liste;
}

export function maliyet(d) {
  const kat = d.tur === "depo" ? 8 : d.tur === "sube" ? 12 : d.tur === "nokta" ? 20 : 0;
  let t = d.mesafe * kat;
  for (const c of d.alt) t += maliyet(c);
  return t;
}
```

#### Gezinmenin Ayrılması

Yineleyici sürümünde gezinme tek bir üreteçtedir. İşlemler artık ağaç görmez, düğüm dizisi görür.

```js
// yineleyici/gez.mjs — gezinme kodu tek yerde, derine arama sirasiyla dugum verir
export function* gez(dugum) {
  yield dugum;
  for (const c of dugum.alt) yield* gez(c);
}
```

```js
// yineleyici/islemler.mjs — bes islem, hicbirinde gezinme kodu yok
import { gez } from "./gez.mjs";

export function toplamMesafe(kok) {
  let t = 0;
  for (const d of gez(kok)) t += d.mesafe;
  return t;
}

export function enUzunBekleme(kok) {
  let e = 0;
  for (const d of gez(kok)) e = Math.max(e, d.bekleme);
  return e;
}

export function darKapasite(kok) {
  let n = 0;
  for (const d of gez(kok)) {
    switch (d.tur) {
      case "depo":
      case "sube":
        n += d.kapasite < 500 ? 1 : 0;
        break;
      default:
        break;
    }
  }
  return n;
}

export function etiketler(kok) {
  const liste = [];
  for (const d of gez(kok)) {
    switch (d.tur) {
      case "merkez":
        liste.push(`M:${d.ad}`);
        break;
      case "depo":
        liste.push(`D:${d.ad}`);
        break;
      case "sube":
        liste.push(`S:${d.ad}`);
        break;
      default:
        liste.push(`N:${d.ad}`);
        break;
    }
  }
  return liste;
}

export function maliyet(kok) {
  let t = 0;
  for (const d of gez(kok)) {
    switch (d.tur) {
      case "depo":
        t += d.mesafe * 8;
        break;
      case "sube":
        t += d.mesafe * 12;
        break;
      case "nokta":
        t += d.mesafe * 20;
        break;
      default:
        break;
    }
  }
  return t;
}
```

Gezinme kayboldu, ama tür ayrımı kalmaya devam ediyor: üç işlem hâlâ `d.tur` üzerinde dallanıyor.
Ziyaretçi bu üçünü hedefler. Türün seçimi tek bir yere alınır ve eksik tür sessizce
geçilmez.

```js
// ziyaretci/kabul.mjs — dugum kendi turunun yontemini secer, eksikse hata firlatir
export function kabul(dugum, ziyaretci) {
  const yontem = ziyaretci[dugum.tur];
  if (yontem === undefined) throw new TypeError(`${dugum.tur} icin ziyaretci yontemi yok`);
  return yontem(dugum);
}
```

```js
// ziyaretci/ziyaretciler.mjs — her islem her dugum turu icin bir yontem tasir
export const maliyet = {
  merkez: () => 0,
  depo: (d) => d.mesafe * 8,
  sube: (d) => d.mesafe * 12,
  nokta: (d) => d.mesafe * 20,
};

export const etiket = {
  merkez: (d) => `M:${d.ad}`,
  depo: (d) => `D:${d.ad}`,
  sube: (d) => `S:${d.ad}`,
  nokta: (d) => `N:${d.ad}`,
};

export const darlik = {
  merkez: () => 0,
  depo: (d) => (d.kapasite < 500 ? 1 : 0),
  sube: (d) => (d.kapasite < 500 ? 1 : 0),
  nokta: () => 0,
};
```

#### Üç Sürümün Eşitliği

```js
// kosum.mjs — uc surumun ayni agac uzerinde ayni sonucu verdigini dogrular
import { ROTA } from "./agac.mjs";
import * as gezinme from "./gezinme/islemler.mjs";
import * as yineleyici from "./yineleyici/islemler.mjs";
import { gez } from "./yineleyici/gez.mjs";
import { kabul } from "./ziyaretci/kabul.mjs";
import { darlik, etiket, maliyet } from "./ziyaretci/ziyaretciler.mjs";

const ziyaretle = (z, birlestir, baslangic) => {
  let sonuc = baslangic;
  for (const d of gez(ROTA)) sonuc = birlestir(sonuc, kabul(d, z));
  return sonuc;
};

const sonuclar = {
  gezinme: [
    gezinme.toplamMesafe(ROTA),
    gezinme.enUzunBekleme(ROTA),
    gezinme.darKapasite(ROTA),
    gezinme.etiketler(ROTA).join(""),
    gezinme.maliyet(ROTA),
  ],
  yineleyici: [
    yineleyici.toplamMesafe(ROTA),
    yineleyici.enUzunBekleme(ROTA),
    yineleyici.darKapasite(ROTA),
    yineleyici.etiketler(ROTA).join(""),
    yineleyici.maliyet(ROTA),
  ],
};
sonuclar.ziyaretci = [
  sonuclar.yineleyici[0],
  sonuclar.yineleyici[1],
  ziyaretle(darlik, (a, b) => a + b, 0),
  ziyaretle(etiket, (a, b) => a + b, ""),
  ziyaretle(maliyet, (a, b) => a + b, 0),
];

for (const [ad, s] of Object.entries(sonuclar)) {
  console.log(`${ad.padEnd(11)} mesafe=${s[0]} bekleme=${s[1]} dar=${s[2]} maliyet=${s[4]}`);
}
console.log(`etiketler: ${sonuclar.gezinme[3]}`);
const ayrik = new Set(Object.values(sonuclar).map((s) => s.join("|")));
console.log(`farkli sonuc veren surum kumesi = ${ayrik.size}`);
```

```
gezinme     mesafe=545 bekleme=7 dar=2 maliyet=5260
yineleyici  mesafe=545 bekleme=7 dar=2 maliyet=5260
ziyaretci   mesafe=545 bekleme=7 dar=2 maliyet=5260
etiketler: M:MRKD:DPO-AS:SB-A1N:NK-A1aN:NK-A1bS:SB-A2N:NK-A2aD:DPO-BS:SB-B1N:NK-B1a
farkli sonuc veren surum kumesi = 1
```

Ayrık sonuç kümesi bir: üç sürüm aynı beş değeri veriyor, karşılaştırma geçerli.

#### Gezinme Kodu ve Tür Ayrımı

```js
// sayim.mjs — gezinme kodu tasiyan govde sayisi ve tur ayirt eden yer sayisi
import { readFileSync, readdirSync } from "node:fs";

const say = (metin, kalip) => (metin.match(kalip) ?? []).length;

for (const dizin of ["gezinme", "yineleyici", "ziyaretci"]) {
  let gezinme = 0;
  let turAyirt = 0;
  for (const d of readdirSync(dizin).sort()) {
    const metin = readFileSync(`${dizin}/${d}`, "utf8").replace(/^\/\/.*$/gm, "");
    gezinme += say(metin, /\.alt\b/g);
    turAyirt += say(metin, /\.tur\b|\bswitch\b/g);
  }
  console.log(`${dizin.padEnd(11)} gezinme kodu tasiyan yer=${gezinme}  tur ayirt eden yer=${turAyirt}`);
}
```

```
gezinme     gezinme kodu tasiyan yer=5  tur ayirt eden yer=7
yineleyici  gezinme kodu tasiyan yer=1  tur ayirt eden yer=6
ziyaretci   gezinme kodu tasiyan yer=0  tur ayirt eden yer=2
```

Beş, bir, sıfır. Yineleyici gezinmeyi beş yerden bire indirdi ama tür ayrımına dokunmadı: yedi
yerden altıya. Ziyaretçi tür ayrımını ikiye indirdi ve ikisi de aynı dosyadadır. İki kalıp iki
ayrı ekseni temizliyor; birbirinin yerine geçmiyorlar.

#### Gezinme Sırası Değiştiğinde

Etiket listesinin bölge sırasına göre çıkması isteniyor: aynı düzeydeki düğümler bir arada, yani
enine arama. Yineleyici sürümünde bu tek dosyalık bir değişikliktir.

```js
// sira.mjs — gezinme sirasi enine aramaya cevrilir; yalniz gez.mjs duzenlenir
import { cpSync, readdirSync, writeFileSync } from "node:fs";
import { ROTA } from "./agac.mjs";

cpSync("yineleyici", "yineleyici-enine", { recursive: true });
writeFileSync(
  "yineleyici-enine/gez.mjs",
  `// yineleyici/gez.mjs — gezinme kodu tek yerde, enine arama sirasiyla dugum verir
export function* gez(dugum) {
  const kuyruk = [dugum];
  while (kuyruk.length > 0) {
    const d = kuyruk.shift();
    yield d;
    for (const c of d.alt) kuyruk.push(c);
  }
}
`,
);

const eski = await import("./yineleyici/islemler.mjs");
const yeni = await import("./yineleyici-enine/islemler.mjs");
console.log(`duzenlenen dosya: yineleyici=1 (${readdirSync("yineleyici").length} dosyadan)  gezinme=5 govde`);
for (const [ad, m] of [["derine", eski], ["enine", yeni]]) {
  console.log(`${ad.padEnd(7)} mesafe=${m.toplamMesafe(ROTA)} bekleme=${m.enUzunBekleme(ROTA)} dar=${m.darKapasite(ROTA)} maliyet=${m.maliyet(ROTA)}`);
  console.log(`${ad.padEnd(7)} etiketler=${m.etiketler(ROTA).join("")}`);
}
```

```
duzenlenen dosya: yineleyici=1 (2 dosyadan)  gezinme=5 govde
derine  mesafe=545 bekleme=7 dar=2 maliyet=5260
derine  etiketler=M:MRKD:DPO-AS:SB-A1N:NK-A1aN:NK-A1bS:SB-A2N:NK-A2aD:DPO-BS:SB-B1N:NK-B1a
enine   mesafe=545 bekleme=7 dar=2 maliyet=5260
enine   etiketler=M:MRKD:DPO-AD:DPO-BS:SB-A1S:SB-A2S:SB-B1N:NK-A1aN:NK-A1bN:NK-A2aN:NK-B1a
```

Bir dosyaya karşı beş gövde. Sıradan bağımsız dört işlemin sonucu değişmedi; sıraya bağlı olan
etiket listesi yeni düzeni verdi. Aynı değişikliği gezinme sürümünde yapmak beş özyinelemeyi
birer birer kuyruk döngüsüne çevirmek anlamına gelir, çünkü sırayı bilen beş gövde vardır.

#### Yeni Düğüm Türü Eklendiğinde

Ağaca gümrük aktarma noktası ekleniyor. Beklenen davranış belli: gümrük düğümünün maliyet
katsayısı 15, etiket ön eki `G`, kapasite denetimi diğer aktarma noktaları gibi.

```js
// yenitur.mjs — agaca gumruk dugumu eklenir; iki surumun tepkisi sayilir
import { ROTA } from "./agac.mjs";
import * as yineleyici from "./yineleyici/islemler.mjs";
import { gez } from "./yineleyici/gez.mjs";
import { kabul } from "./ziyaretci/kabul.mjs";
import { darlik, etiket, maliyet } from "./ziyaretci/ziyaretciler.mjs";

const GUMRUK = { tur: "gumruk", ad: "GMR", mesafe: 40, bekleme: 8, kapasite: 200, alt: [] };
ROTA.alt[1].alt.push(GUMRUK);

const BEKLENEN = { dar: 3, maliyet: 5860, etiketGumruk: "G:GMR" };

const ziyaretle = (z, birlestir, baslangic) => {
  let sonuc = baslangic;
  for (const d of gez(ROTA)) sonuc = birlestir(sonuc, kabul(d, z));
  return sonuc;
};

let sessizYanlis = 0;
const y = { dar: yineleyici.darKapasite(ROTA), maliyet: yineleyici.maliyet(ROTA), etiket: yineleyici.etiketler(ROTA).join("") };
if (y.dar !== BEKLENEN.dar) sessizYanlis += 1;
if (y.maliyet !== BEKLENEN.maliyet) sessizYanlis += 1;
if (y.etiket.includes(BEKLENEN.etiketGumruk) === false) sessizYanlis += 1;
console.log(`yineleyici+switch  dar=${y.dar} (beklenen ${BEKLENEN.dar})  maliyet=${y.maliyet} (beklenen ${BEKLENEN.maliyet})  gumruk etiketi=${y.etiket.match(/[A-Z]:GMR/)[0]}`);

let firlatan = 0;
for (const [ad, z, birlestir, bas] of [["darlik", darlik, (a, b) => a + b, 0], ["maliyet", maliyet, (a, b) => a + b, 0], ["etiket", etiket, (a, b) => a + b, ""]]) {
  try {
    ziyaretle(z, birlestir, bas);
    console.log(`ziyaretci/${ad} sessizce tamamlandi`);
  } catch (e) {
    firlatan += 1;
    console.log(`ziyaretci/${ad.padEnd(7)} ${e.constructor.name}: ${e.message}`);
  }
}

console.log(`sessizce yanlis sonuc veren islem: yineleyici+switch=${sessizYanlis}  ziyaretci=0`);
console.log(`eksik turu bildiren islem: yineleyici+switch=0  ziyaretci=${firlatan}`);
console.log(`yeni tur icin duzenlenmesi gereken yer: yineleyici+switch=3 switch govdesi  ziyaretci=3 ziyaretci nesnesi`);
```

```
yineleyici+switch  dar=2 (beklenen 3)  maliyet=5260 (beklenen 5860)  gumruk etiketi=N:GMR
ziyaretci/darlik  TypeError: gumruk icin ziyaretci yontemi yok
ziyaretci/maliyet TypeError: gumruk icin ziyaretci yontemi yok
ziyaretci/etiket  TypeError: gumruk icin ziyaretci yontemi yok
sessizce yanlis sonuc veren islem: yineleyici+switch=3  ziyaretci=0
eksik turu bildiren islem: yineleyici+switch=0  ziyaretci=3
yeni tur icin duzenlenmesi gereken yer: yineleyici+switch=3 switch govdesi  ziyaretci=3 ziyaretci nesnesi
```

Üçe karşı sıfır ve sıfıra karşı üç. Düzenlenmesi gereken yer sayısı ikisinde de üçtür — bu,
Programlama Paradigmaları kursunda kurulan ifade problemidir ve ziyaretçi onu çözmez, yalnızca
yönünü belirler: yeni işlem ucuz, yeni tür pahalıdır. Kalıbın verdiği şey pahalılığın
**görünürlüğü**dür. Anahtar gövdesindeki `default` dalı gümrüğü teslim noktası saydı, maliyeti
600 kuruş eksik hesapladı, kapasite denetiminden düşürdü ve hiçbir uyarı üretmedi. Ziyaretçi
sürümü üç işlemde de eksik yöntemi adıyla bildirdi.

Bedel iki kalemdir. Birincisi çağrı basamağıdır: bir düğümün maliyetine ulaşmak için `kabul`
üzerinden geçmek gerekir, yani gövdeye giden yol bir çağrı uzar. İkincisi yeni işlemin bedeli:
ziyaretçi sürümünde yeni bir işlem, kaç düğüm türü varsa o kadar yöntem yazmayı gerektirir —
burada dört. Anahtar gövdesinde `default` dalı bu zorunluluğu kaldırır, karşılığında sessizliği
getirir.

#### Özet

- Yineleyici gezinmeyi işlemden ayırır, ziyaretçi tür ayrımını işlemden ayırır; ikisi ayrı
  ekseni temizler ve birbirinin yerine geçmez.
- Üç sürüm aynı ağaçta aynı beş değeri üretti; ayrık sonuç kümesi 1 çıktı.
- Gezinme kodu taşıyan yer sayısı 5'ten 1'e, tür ayırt eden yer sayısı 7'den 2'ye indi.
- Gezinme sırası enine aramaya çevrilirken yineleyici sürümünde 1 dosya düzenlendi; aynı
  değişiklik gezinme sürümünde 5 gövdeyi düzenlemeyi gerektirir.
- Yeni düğüm türü eklendiğinde anahtar gövdeli sürüm 3 işlemde sessizce yanlış sonuç verdi,
  ziyaretçi sürümü 3 işlemde eksik yöntemi bildirdi; düzenlenmesi gereken yer sayısı ikisinde
  de 3'tür.
- Bedel: `kabul` bir çağrı basamağı ekler ve yeni bir işlem, düğüm türü sayısı kadar yöntem
  yazmayı gerektirir.

#### Sonraki Adım

Ziyaretçi bir işlemi ağacın her düğümüne uyguluyor; düğümler birbirini tanımıyor, hepsi
gezinmenin verdiği sırayla ele alınıyor. Kitaplığın operatör ekranında ise nesneler birbirini
doğrudan tanıyor. Taşıyıcı seçimi, tarife özeti, indirim kutusu ve teslim tarihi alanı
birbirini haberdar ediyor: taşıyıcı değişince tarife yenileniyor, tarife değişince indirim
yeniden hesaplanıyor, indirim değişince teslim tarihi güncellenebiliyor. Dört alanın her biri
diğer üçünü ithal ediyor. Sonraki ders bu karşılıklı bağların sayısını ölçer, bağları tek bir
nesnede toplayıp aynı sayıyı yeniden hesaplar ve düzenleme oturumunun durumunu kapsüllemeyi
bozmadan saklamanın maliyetini ayrıca sayar.

### Arabulucu ve Anımsatıcı

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/arabulucu-ve-animsatici

Operatör formundaki dört alanın birbirini doğrudan tanıması ile yönlendirmenin tek nesnede toplanmasının karşılaştırılması: alanlar arası bağ sayısı, güncelleme çağrısı sayısı, bağ sayısının alan sayısıyla büyümesi; ayrıca durum saklamanın dışarıdan erişilen iç alan sayısı ve fişin taşıdığı alan sayısıyla ölçülmesi.

Ziyaretçi bir işlemi ağacın her düğümüne uyguluyordu; düğümler birbirini tanımıyordu. Operatör
ekranında ise nesneler birbirini doğrudan tanıyor. Taşıyıcı seçimi, tarife özeti, indirim kutusu
ve teslim tarihi alanı birbirini haberdar ediyor: taşıyıcı değişince tarife yenileniyor, tarife
değişince indirim yeniden hesaplanıyor, indirim değişince teslim tarihi güncelleniyor. Her alan
kendisinden sonra etkilenen alanları tanıyor ve doğrudan çağırıyor.

Bu dersin iki kalıbı iki ayrı sorunu hedefler. **Arabulucu** (mediator) alanlar arasındaki
yönlendirmeyi tek bir nesneye toplar; alanlar birbirini tanımaz. **Anımsatıcı** (memento) bir
nesnenin durumunu, iç alanlarını dışarıya açmadan saklanabilir bir fişe çevirir. Ölçülecek
sayılar alanlar arası bağ sayısı, güncelleme çağrısı sayısı, dışarıdan erişilen iç alan sayısı
ve fişin taşıdığı alan sayısıdır.

#### Alanların Birbirini Tanıması

```sh
mkdir -p dogrudan arabulucu animsatici
```

```js
// dogrudan/form.mjs — her alan kendisinden sonra etkilenen alanlari dogrudan tanir
const TARIFE = { yurtici: 8400, ekspres: 12600, yurtdisi: 21000 };

export function form() {
  const d = { tasiyici: "yurtici", tarife: 0, indirim: 0, teslimGun: 0 };
  let cagri = 0;

  const teslimTarihi = {
    guncelle() {
      cagri += 1;
      d.teslimGun = (d.tasiyici === "ekspres" ? 1 : 3) + (d.indirim >= 10 ? 1 : 0);
    },
  };
  const indirim = {
    guncelle() {
      cagri += 1;
      d.indirim = d.tarife > 10000 ? 10 : 5;
      teslimTarihi.guncelle();
    },
  };
  const tarife = {
    guncelle() {
      cagri += 1;
      d.tarife = TARIFE[d.tasiyici];
      indirim.guncelle();
      teslimTarihi.guncelle();
    },
  };
  const tasiyici = {
    sec(ad) {
      cagri += 1;
      d.tasiyici = ad;
      tarife.guncelle();
      indirim.guncelle();
      teslimTarihi.guncelle();
    },
  };
  return { deger: d, sec: (ad) => tasiyici.sec(ad), sayac: () => cagri };
}
```

Her alan, kendisinden sonra etkilenebilecek her alanı çağırıyor. Bu, gözlemci dersindekinden
farklı bir durumdur: orada haber veren dinleyicilerini tanımıyordu, burada her alan hem haber
veriyor hem haber alıyor ve bağlar iki yönlü.

#### Yönlendirmenin Tek Nesnede Toplanması

Arabulucu sürümünde alanlar birbirini hiç görmez; her biri yalnız kendi hesabını yapar.

```js
// arabulucu/alanlar.mjs — hicbir alan digerini tanimaz, yalniz kendi isini yapar
const TARIFE = { yurtici: 8400, ekspres: 12600, yurtdisi: 21000 };

export const alanlar = (say) => ({
  tarife: {
    guncelle(d) {
      say();
      d.tarife = TARIFE[d.tasiyici];
    },
  },
  indirim: {
    guncelle(d) {
      say();
      d.indirim = d.tarife > 10000 ? 10 : 5;
    },
  },
  teslimTarihi: {
    guncelle(d) {
      say();
      d.teslimGun = (d.tasiyici === "ekspres" ? 1 : 3) + (d.indirim >= 10 ? 1 : 0);
    },
  },
});
```

```js
// arabulucu/form.mjs — alanlar arasindaki yonlendirme yalniz burada
import { alanlar } from "./alanlar.mjs";

const SIRA = ["tarife", "indirim", "teslimTarihi"];

export function form() {
  const d = { tasiyici: "yurtici", tarife: 0, indirim: 0, teslimGun: 0 };
  let cagri = 0;
  const say = () => {
    cagri += 1;
  };
  const a = alanlar(say);
  const bildir = (kaynak) => {
    const bas = kaynak === "tasiyici" ? 0 : SIRA.indexOf(kaynak) + 1;
    for (const ad of SIRA.slice(bas)) a[ad].guncelle(d);
  };
  return {
    deger: d,
    sec(ad) {
      say();
      d.tasiyici = ad;
      bildir("tasiyici");
    },
    sayac: () => cagri,
  };
}
```

#### Değerlerin Eşitliği ve Çağrı Sayısı

```js
// kosum.mjs — iki surumu ayni secimlerle kosar, deger ve cagri sayisini karsilastirir
import { form as dogrudanForm } from "./dogrudan/form.mjs";
import { form as arabulucuForm } from "./arabulucu/form.mjs";

let sapma = 0;
for (const secim of ["ekspres", "yurtdisi", "yurtici"]) {
  const a = dogrudanForm();
  const b = arabulucuForm();
  a.sec(secim);
  b.sec(secim);
  const ozet = (f) => Object.entries(f.deger).map(([k, v]) => `${k}=${v}`).join(" ");
  if (ozet(a) !== ozet(b)) sapma += 1;
  console.log(`${secim.padEnd(9)} ${ozet(a)}  |  guncelleme cagrisi dogrudan=${a.sayac()} arabulucu=${b.sayac()}`);
}
console.log(`iki surum arasindaki deger sapmasi = ${sapma}`);
```

```
ekspres   tasiyici=ekspres tarife=12600 indirim=10 teslimGun=2  |  guncelleme cagrisi dogrudan=8 arabulucu=4
yurtdisi  tasiyici=yurtdisi tarife=21000 indirim=10 teslimGun=4  |  guncelleme cagrisi dogrudan=8 arabulucu=4
yurtici   tasiyici=yurtici tarife=8400 indirim=5 teslimGun=3  |  guncelleme cagrisi dogrudan=8 arabulucu=4
iki surum arasindaki deger sapmasi = 0
```

Değerler birebir aynı, güncelleme çağrısı sayısı sekize karşı dört. Fazla dört çağrı yinelenen
tetiklemedir: teslim tarihi bir seçim başına üç kez hesaplanıyor, çünkü onu üç ayrı alan çağırıyor.
İlk iki hesabın sonucu üçüncüsü tarafından ezildiği için değer doğru çıkıyor — bu, sırf şansa
bağlı bir doğruluktur. Alan sayısı arttıkça yinelenen tetikleme sayısı da artar ve sıra
garantisi kaybolur.

#### Bağ Sayısının Büyümesi

```js
// bag.mjs — alanlar arasindaki bag sayisi ve alan sayisiyla buyumesi
import { readFileSync } from "node:fs";

const dogrudanMetin = readFileSync("dogrudan/form.mjs", "utf8");
const arabulucuMetin = readFileSync("arabulucu/form.mjs", "utf8");

const kardesBag = (dogrudanMetin.match(/\b(tarife|indirim|teslimTarihi)\.guncelle\(\)/g) ?? []).length;
const arabulucuBag = (arabulucuMetin.match(/^const SIRA = \[(.*)\];$/m)[1].split(",")).length;

console.log(`dogrudan  alanlar arasi bag=${kardesBag}  arabulucu nesnesi=0`);
console.log(`arabulucu alanlar arasi bag=0  arabulucunun tanidigi alan=${arabulucuBag}`);
console.log(`N=4 icin formul denetimi: N(N-1)/2=${(4 * 3) / 2}  N-1=${4 - 1}`);
for (const n of [4, 6, 8]) {
  console.log(`N=${n}  dogrudan bag=${(n * (n - 1)) / 2}  arabulucu bag=${n - 1}`);
}
```

```
dogrudan  alanlar arasi bag=6  arabulucu nesnesi=0
arabulucu alanlar arasi bag=0  arabulucunun tanidigi alan=3
N=4 icin formul denetimi: N(N-1)/2=6  N-1=3
N=4  dogrudan bag=6  arabulucu bag=3
N=6  dogrudan bag=15  arabulucu bag=5
N=8  dogrudan bag=28  arabulucu bag=7
```

Ölçülen altı bağ, dört alanlı tam zincir için $N(N-1)/2$ formülünün verdiği sayıya eşit;
arabulucu sürümünde ölçülen üç bağ $N-1$ formülünü doğruluyor. İki formülün büyüme hızı farklı:
sekiz alanda yirmi sekize karşı yedi. Bağ sayısındaki bu düşüş bedelsiz değildir. Arabulucu bir
nesne ve bir dosya ekler; daha önemlisi, bilgi alanlardan çıkıp arabulucuda birikir. `SIRA`
dizisi artık formun tüm bağımlılık sırasını taşıyan tek yerdir ve alan sayısı arttıkça bu tek
dosyanın değişim nedeni sayısı da artar. Arabulucunun büyümesi, kalıbın bilinen sonucudur:
bağlar azalır, merkezdeki nesne şişer.

#### Durumun Saklanması

Operatör bir düzenleme oturumunda birkaç deneme yapıp beğenmezse başlangıca dönmek istiyor.
Birinci yol, çağıranın alanları kendisi okuyup geri yazmasıdır.

```js
// dogrudan/oturum.mjs — cagiran ic alanlari kendisi okuyup geri yaziyor
export const kopyaAl = (f) => ({
  tasiyici: f.deger.tasiyici,
  tarife: f.deger.tarife,
  indirim: f.deger.indirim,
  teslimGun: f.deger.teslimGun,
});

export const kopyaYaz = (f, k) => {
  f.deger.tasiyici = k.tasiyici;
  f.deger.tarife = k.tarife;
  f.deger.indirim = k.indirim;
  f.deger.teslimGun = k.teslimGun;
};
```

Anımsatıcı sürümünde durum hiç dışarı çıkmaz. Nesne bir fiş verir; fiş yalnızca bir sıra
numarası taşır ve içeriği dışarıdan okunamaz.

```js
// animsatici/oturum.mjs — durum disariya acilmadan saklanir, fis yalniz bir sira numarasi tasir
export function oturum(baslangic) {
  let durum = { ...baslangic };
  const kayitlar = [];
  return {
    yaz(ad, deger) {
      durum[ad] = deger;
    },
    animsat() {
      kayitlar.push({ ...durum });
      return { no: kayitlar.length - 1 };
    },
    geriYukle(fis) {
      if (fis.no in kayitlar === false) return false;
      durum = { ...kayitlar[fis.no] };
      return true;
    },
    ozet: () => Object.entries(durum).map(([k, v]) => `${k}=${v}`).join(" "),
  };
}
```

```js
// saklama.mjs — iki saklama yontemini karsilastirir
import { readFileSync } from "node:fs";
import { form } from "./arabulucu/form.mjs";
import { kopyaAl, kopyaYaz } from "./dogrudan/oturum.mjs";
import { oturum } from "./animsatici/oturum.mjs";

const ozet = (d) => Object.entries(d).map(([k, v]) => `${k}=${v}`).join(" ");

const f = form();
f.sec("yurtici");
const fis1 = kopyaAl(f);
f.sec("yurtdisi");
const bozuk = ozet(f.deger);
kopyaYaz(f, fis1);
console.log(`dogrudan  fis alan sayisi=${Object.keys(fis1).length}  geri yukleme sonrasi: ${ozet(f.deger)}`);

const o = oturum({ tasiyici: "yurtici", tarife: 8400, indirim: 5, teslimGun: 3 });
const fis2 = o.animsat();
o.yaz("tasiyici", "yurtdisi");
o.yaz("tarife", 21000);
o.yaz("indirim", 10);
o.yaz("teslimGun", 4);
const yuklendi = o.geriYukle(fis2);
console.log(`animsatici fis alan sayisi=${Object.keys(fis2).length}  geri yukleme=${yuklendi ? 1 : 0}  sonrasi: ${o.ozet()}`);
console.log(`degistirilmis ara durum: ${bozuk}`);

const disErisim = (yol) => (readFileSync(yol, "utf8").match(/f\.deger\.\w+/g) ?? []).length;
console.log(`disaridan erisilen ic alan: dogrudan=${disErisim("dogrudan/oturum.mjs")}  animsatici=${disErisim("animsatici/oturum.mjs")}`);
console.log(`gecersiz fis denemesi: animsatici geriYukle({ no: 9 })=${o.geriYukle({ no: 9 }) ? 1 : 0}`);
```

```
dogrudan  fis alan sayisi=4  geri yukleme sonrasi: tasiyici=yurtici tarife=8400 indirim=5 teslimGun=3
animsatici fis alan sayisi=1  geri yukleme=1  sonrasi: tasiyici=yurtici tarife=8400 indirim=5 teslimGun=3
degistirilmis ara durum: tasiyici=yurtdisi tarife=21000 indirim=10 teslimGun=4
disaridan erisilen ic alan: dogrudan=8  animsatici=0
gecersiz fis denemesi: animsatici geriYukle({ no: 9 })=0
```

İki yöntem de aynı başlangıç durumunu geri getirdi; ara durumun gerçekten değiştiği üçüncü
satırda görülüyor. Fark ölçüde: dışarıdan erişilen iç alan sayısı sekize karşı sıfır, fişin
taşıdığı alan sayısı dörde karşı bir. Birinci yöntem, alan başına bir okuma ve bir yazma
gerektirdiği için beşinci bir alan eklendiğinde iki satır daha büyür; anımsatıcı sürümünde
`{ ...durum }` alan sayısından bağımsızdır ve düzenlenmez.

Bedel iki yerde. Birincisi bellektir: her fiş durumun tam bir kopyasını saklar, kayıt sayısı
kadar kopya birikir ve bunları temizleyecek bir kural yoktur. İkincisi fişin geçerliliğidir:
son satır, var olmayan bir numarayla yapılan geri yükleme denemesinin sessizce başarısız
olduğunu gösteriyor. Fiş içeriğini gizlediği için çağıran fişin hâlâ anlamlı olup olmadığını
sınayamaz; kalıp bu sınamayı nesnenin kendisine bırakır.

#### Özet

- Arabulucu alanlar arasındaki yönlendirmeyi tek nesneye toplar; alanlar birbirini tanımaz,
  yalnız kendi hesabını yapar.
- Değerler iki sürümde birebir aynı çıktı; güncelleme çağrısı sayısı 8'den 4'e indi, fazladan
  dört çağrı yinelenen tetiklemeydi.
- Alanlar arası bağ sayısı 6'dan 3'e indi; ölçüm $N(N-1)/2$ ve $N-1$ formüllerini doğruladı,
  sekiz alanda fark 28'e karşı 7 oluyor. Bedel, bağımlılık sırasını tek başına taşıyan bir
  arabulucu nesnesidir.
- Anımsatıcı durumu dışarı açmadan saklar: dışarıdan erişilen iç alan sayısı 8'den 0'a,
  fişin taşıdığı alan sayısı 4'ten 1'e indi ve iki yöntem aynı başlangıç durumunu geri getirdi.
- Anımsatıcının bedeli her fişin tam bir durum kopyası tutması ve geçersiz bir fişin sessizce
  başarısız olmasıdır.

#### Sonraki Adım

Arabulucu bir sırayı, anımsatıcı bir kopyayı saklıyor; ikisi de veri tutuyor ama veri hâlâ
kodun içinde yazılı. Tarife kuralları tarafında bunun sınırına gelindi: her yeni koşullu kural
bir dosya değişikliği demek ve kuralları yazan kişi geliştirici değil, tarife birimi. Kuralın
metin olarak yazılıp çalışma zamanında yorumlanması isteniyor. Aynı bölümde ikinci bir soru var:
bir gönderiye hiçbir indirim uygulanmadığında sonucun `null` olmasıyla, hiçbir şey yapmayan bir
indirim nesnesi olması arasındaki fark nedir. Sonraki ders küçük bir kural dilini yorumlayan
nesne ağacını kurar, yeni kural eklendiğinde düzenlenen dosya sayısını ölçer ve boş değer
denetimi sayısını boş nesneyle karşılaştırır.

### Yorumlayıcı ve Boş Nesne

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/yorumlayici-ve-bos-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.

### Kalıp Kötüye Kullanımı

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/kalip-kotuye-kullanimi

Beş davranışsal kalıbın tek gerçekleştirimle uygulandığı sürümün kalıpsız sürümle aynı ölçülerle karşılaştırılması: dosya sayısı, anlamlı satır, ithal kapanışı, en karmaşık gövde ve çağrı derinliği bedelleri ile sıfır kalan kazançlar; konunun dokuz dersinde ölçülen eşiklerin toplu tablosu.

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ı. Bu sayılar bir eşiğin varlığını gösteriyor, ama eşiğin **altında** ne olduğu henüz
ölçülmedi.

Bu ders aynı ölçüleri kalıbın aleyhine koşturur. Beş kalıp — strateji, gözlemci, şablon yöntem,
durum, sorumluluk zinciri — tek gerçekleştirimle uygulanır ve aynı davranışı üreten kalıpsız
sürümle karşılaştırılır. Kalıplı sürümde tek tarife, tek gözlemci, tek değişen adım, iki durum
ve tek işleyici var. Beklenen sonuç şudur: bedel kalemleri aynı kalır, kazanç kalemleri sıfırlanır.

#### Aynı Davranış, İki Sürüm

Ücretlendirme şu adımlardan geçiyor: gönderi onaylanmış olmalı, taban ücret ağırlıktan
hesaplanmalı, bölge katsayısı uygulanmalı, hacim indirimi varsa düşülmeli, asgari ücret
uygulanmalı ve sonuç günlüğe yazılmalı.

```sh
mkdir -p duz kalipli

cat > kalipli/durumlar.mjs <<'SON'
// kalipli/durumlar.mjs — iki durumlu durum makinesi
const durum = (ad, ucretlenebilir) => ({ ad, ucretlenebilir });

export const DURUMLAR = { taslak: durum("taslak", false), onaylandi: durum("onaylandi", true) };
SON

cat > kalipli/kurallar.mjs <<'SON'
// kalipli/kurallar.mjs — tek isleyicili zincir
export const hacim = { ad: "hacim", keser: false, uygunMu: (g) => g.agirlik >= 20, oran: () => 8 };

export const SIRA = [hacim];
SON

cat > kalipli/zincir.mjs <<'SON'
// kalipli/zincir.mjs — zincir yurutucusu
export function indirim(kurallar, gonderi) {
  let oran = 0;
  for (const k of kurallar) {
    if (k.uygunMu(gonderi) === false) continue;
    oran += k.oran(gonderi);
    if (k.keser) break;
  }
  return oran;
}
SON

cat > kalipli/gozlemci.mjs <<'SON'
// kalipli/gozlemci.mjs — tek gozlemcili olay yayimi
export const yayinci = () => {
  const aboneler = [];
  return {
    abone: (d) => aboneler.push(d),
    bildir(olay) {
      for (const d of aboneler) d(olay);
    },
  };
};
SON
```

```js
// derinlik.mjs — o anki cagri yiginda kendi dosyalarimiza ait cerceve sayisi
export const derinlik = () => new Error().stack.split("\n").filter((s) => s.includes(".mjs:")).length;
```

```js
// veri.mjs — ucretlendirilecek gonderiler
export const GONDERILER = [
  { kod: "GN-1", agirlik: 4, bolge: "1", durum: "onaylandi" },
  { kod: "GN-2", agirlik: 25, bolge: "2", durum: "onaylandi" },
  { kod: "GN-3", agirlik: 9, bolge: "3", durum: "taslak" },
];
```

Kalıpsız sürüm tek dosyadır ve altı adımı sırayla yapar.

```js
// duz/ucretle.mjs — ayni davranis tek dosyada, kalipsiz
import { derinlik } from "../derinlik.mjs";

const BOLGE = { "1": 100, "2": 130, "3": 175 };

export function ucretlendirici(gunluk) {
  return {
    ucretle(gonderi) {
      if (gonderi.durum !== "onaylandi") return { hata: "onaylanmadi" };
      const d = derinlik();
      let tutar = Math.round(((2500 + 420 * Math.ceil(gonderi.agirlik)) * BOLGE[gonderi.bolge]) / 100);
      if (gonderi.agirlik >= 20) tutar -= Math.round((tutar * 8) / 100);
      tutar = Math.max(tutar, 3900);
      gunluk.push(`ucret ${gonderi.kod} ${tutar}`);
      return { tutar, derinlik: d };
    },
  };
}
```

Kalıplı sürümde her sorumluluk kendi nesnesindedir. Strateji tek gerçekleştirim taşır ve taban
ücreti hesaplarken çağrı derinliğini kaydeder.

```js
// kalipli/tarife.mjs — tek gerceklestirimi olan strateji
import { derinlik } from "../derinlik.mjs";

export const standart = {
  ad: "standart",
  asgari: 3900,
  ucretliAgirlik: (g) => Math.ceil(g.agirlik),
  tabanUcret(a) {
    this.sonDerinlik = derinlik();
    return 2500 + 420 * a;
  },
};
```

```js
// kalipli/sablon.mjs — tek adimi degisen sablon yontem
const BOLGE = { "1": 100, "2": 130, "3": 175 };

export function hesapla(tarife, gonderi, indirimOrani) {
  const a = tarife.ucretliAgirlik(gonderi);
  let tutar = Math.round((tarife.tabanUcret(a) * BOLGE[gonderi.bolge]) / 100);
  tutar -= Math.round((tutar * indirimOrani) / 100);
  return Math.max(tutar, tarife.asgari);
}
```

```js
// kalipli/kur.mjs — bilesim koku: bes kalibi birbirine baglar
import { standart } from "./tarife.mjs";
import { hesapla } from "./sablon.mjs";
import { DURUMLAR } from "./durumlar.mjs";
import { SIRA } from "./kurallar.mjs";
import { indirim } from "./zincir.mjs";
import { yayinci } from "./gozlemci.mjs";

export function ucretlendirici(gunluk) {
  const y = yayinci();
  y.abone((olay) => gunluk.push(`ucret ${olay.kod} ${olay.tutar}`));
  return {
    ucretle(gonderi) {
      const d = DURUMLAR[gonderi.durum];
      if (d === undefined || d.ucretlenebilir === false) return { hata: "onaylanmadi" };
      const tutar = hesapla(standart, gonderi, indirim(SIRA, gonderi));
      y.bildir({ kod: gonderi.kod, tutar });
      return { tutar, derinlik: standart.sonDerinlik };
    },
  };
}
```

#### Davranış Eşitliği ve Çağrı Derinliği

```js
// kosum.mjs — iki surumu ayni gonderilerle kosar, tutar ve derinligi karsilastirir
import { GONDERILER } from "./veri.mjs";
import { ucretlendirici as duzUcret } from "./duz/ucretle.mjs";
import { ucretlendirici as kalipliUcret } from "./kalipli/kur.mjs";

const kosu = (kur) => {
  const gunluk = [];
  const u = kur(gunluk);
  const satir = GONDERILER.map((g) => {
    const r = u.ucretle(g);
    return r.hata === undefined ? `${g.kod}=${r.tutar}` : `${g.kod}=${r.hata}`;
  });
  const derinlik = u.ucretle(GONDERILER[0]).derinlik;
  return { satir, gunluk, derinlik };
};

const a = kosu(duzUcret);
const b = kosu(kalipliUcret);
console.log(`duz     ${a.satir.join(" ")}  gunluk=${a.gunluk.length}  cagri derinligi=${a.derinlik}`);
console.log(`kalipli ${b.satir.join(" ")}  gunluk=${b.gunluk.length}  cagri derinligi=${b.derinlik}`);
console.log(`sapma = ${a.satir.join(" ") === b.satir.join(" ") ? 0 : 1}`);
```

```
duz     GN-1=4180 GN-2=15548 GN-3=onaylanmadi  gunluk=3  cagri derinligi=4
kalipli GN-1=4180 GN-2=15548 GN-3=onaylanmadi  gunluk=3  cagri derinligi=6
sapma = 0
```

Sapma sıfır: iki sürüm aynı üç sonucu, aynı sayıda günlük satırını üretiyor. Beş kalıp
davranışa hiçbir şey eklemedi. Çağrı derinliği dörde karşı altı: taban ücretin hesaplandığı
satıra ulaşmak için iki çağrı daha geçildi.

#### Bedelin ve Kazancın Aynı Tabloda Sayılması

```js
// olc.mjs — kalibin bedelini ve tek gerceklestirimdeki kazancini ayni olculerle sayar
import { readFileSync, readdirSync } from "node:fs";
import { dirname, join, normalize } from "node:path";
import { SIRA } from "./kalipli/kurallar.mjs";
import { DURUMLAR } from "./kalipli/durumlar.mjs";

const KARAR = /\bif\b|&&|\|\||\?|\bcase\b|\bwhile\b|\bfor\b|\bcontinue\b|\bbreak\b/g;
const govde = (yol) => readFileSync(yol, "utf8").replace(/^\/\/.*$/gm, "");

const ithaller = (yol) =>
  [...govde(yol).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.size;
}

const olc = (dizin, giris) => {
  const dosyalar = readdirSync(dizin).sort();
  let satir = 0;
  let enKarmasik = 0;
  for (const d of dosyalar) {
    const m = govde(`${dizin}/${d}`);
    satir += m.split("\n").filter((s) => /[A-Za-z]/.test(s)).length;
    enKarmasik = Math.max(enKarmasik, (m.match(KARAR) ?? []).length + 1);
  }
  return { dosya: dosyalar.length, satir, enKarmasik, kapanis: kapanis(giris) };
};

const a = olc("duz", "duz/ucretle.mjs");
const b = olc("kalipli", "kalipli/kur.mjs");
console.log("olcu                        duz  kalipli");
console.log(`dosya sayisi                ${String(a.dosya).padEnd(4)} ${b.dosya}`);
console.log(`anlamli satir               ${String(a.satir).padEnd(4)} ${b.satir}`);
console.log(`ithal kapanisi              ${String(a.kapanis).padEnd(4)} ${b.kapanis}`);
console.log(`en karmasik govde           ${String(a.enKarmasik).padEnd(4)} ${b.enKarmasik}`);
console.log(`tek kosumda denenen tarife  1    1`);
console.log(`kayitli gozlemci sayisi     1    1`);
console.log(`zincirdeki isleyici sayisi  1    ${SIRA.length}`);
console.log(`durum makinesindeki durum   2    ${Object.keys(DURUMLAR).length}`);
```

```
olcu                        duz  kalipli
dosya sayisi                1    7
anlamli satir               12   47
ithal kapanisi              2    8
en karmasik govde           3    6
tek kosumda denenen tarife  1    1
kayitli gozlemci sayisi     1    1
zincirdeki isleyici sayisi  1    1
durum makinesindeki durum   2    2
```

Tablonun üst yarısı bedeldir, alt yarısı kazançtır. Bedel: yedi dosyaya karşı bir, kırk yedi
satıra karşı on iki, sekiz dosyalık ithal kapanışına karşı iki, en karmaşık gövdede altıya karşı
üç. Son sayı özellikle dikkat çekicidir: kalıplı sürümün **en karmaşık gövdesi**, kalıpsız
sürümün tek gövdesinden daha karmaşıktır. Zincir yürütücüsü tek işleyiciyi gezmek için bir
döngü, bir atlama ve bir kesme koşulu taşır; kalıpsız sürümde aynı indirim tek bir koşuldur.

Alt yarı sıfır kazanç gösteriyor. Strateji dersinde ölçülen "tek koşumda denenen tarife sayısı"
orada 1'e karşı 3'tü; burada 1'e karşı 1. Gözlemci dersinde giden bağ sayısı 4'ten 0'a inmişti;
burada bağ sayısı zaten 1 olduğu için indirilecek bir şey yok, buna karşılık kayıt yapılmadığında
sessizce hiçbir şey yapmama yolu açık kalıyor. Durum dersinde temsil uzayı 32'den 6'ya inmişti;
iki durumda ikiye karşı iki. Sorumluluk zinciri dersinde en karmaşık gövde yedi kuralda 9'dan
6'ya inmişti; tek kuralda 3'ten 6'ya **çıkıyor**.

#### Eşik Tablosu

Dokuz dersin ölçümleri bir arada, her kalıbın karşılığını vermeye başladığı ölçekle birlikte:

| Kalıp | Ölçülen kazanç | Karşılığını verdiği ölçek |
|---|---|---|
| Strateji | tek koşumda denenen algoritma 1 → 3; yeni tarifede düzenlenen dosya 2 → 0 | ikinci algoritma |
| Gözlemci | giden bağ 4 → 0, ithal kapanışı 5 → 1 | ikinci dinleyici |
| Komut | geri alınan işlem 0 → 3, denetim izi kaydı 0 → 8 | geri alma ya da kayıt isteği |
| Şablon yöntem | yinelenen satır 6 → 0, yeni adımda dosya 3 → 1 | ikinci gerçekleştirim |
| Durum | geçersiz geçiş 3 → 0, karmaşıklık 22 → 2, temsil uzayı 32 → 6 | üçüncü durum |
| Sorumluluk zinciri | en karmaşık gövde 9 → 6 (yedi kuralda), 6 → 6 (dört kuralda) | beşinci kural |
| Yineleyici | gezinme kodu taşıyan yer 5 → 1; sıra değişiminde dosya 5 → 1 | ikinci gezinme işlemi |
| Ziyaretçi | sessizce yanlış sonuç veren işlem 3 → 0 | ikinci düğüm türü |
| Arabulucu | alanlar arası bağ 6 → 3, güncelleme çağrısı 8 → 4 | üçüncü alan |
| Anımsatıcı | dışarıdan erişilen iç alan 8 → 0, fiş alan sayısı 4 → 1 | durum saklama isteği |
| Yorumlayıcı | düzenlenen kaynak dosyası 1 → 0 | kuralı geliştirici dışında biri yazacaksa |
| Boş nesne | boş değer denetimi 2 → 0, hata fırlatan çağrı yeri 1 → 0 | ikinci çağrı yeri |

Sağ sütun tek bir cümlenin farklı biçimleridir: kalıp, **ayırdığı eksende ikinci bir örnek
gerçekten varsa** karşılığını verir. İkinci algoritma yoksa strateji bir dosya ve bir dolaylılık
basamağıdır; ikinci dinleyici yoksa gözlemci bir bileşim kökü ve sessiz bir hata yoludur; ikinci
gerçekleştirim yoksa şablon yöntem bir boş kancadır. Kalıbı gerekçelendiren şey kalıbın adı ya da
bilinirliği değil, sol sütundaki sayının sıfırdan farklı olmasıdır.

Bu ölçüt kalıbı sonradan uygulamayı da mümkün kılar. Kalıpsız sürüm, ikinci tarife geldiği gün
strateji sürümüne çevrilebilir; birinci dersteki ölçüm bu çevirinin iki dosya ve on üç satır
tuttuğunu göstermişti. Erken uygulanmış kalıbı geri almak ise yedi dosyayı bire indirmek
demektir ve bu iş, çevirinin tersinden pahalıdır. Kalıbın bedeli peşin, kazancı vadelidir; ölçü
tutulmadığında ödenen bedel geri alınmaz.

#### Özet

- Beş davranışsal kalıp tek gerçekleştirimle uygulandığında kalıpsız sürümle birebir aynı
  sonucu üretti; sapma 0 çıktı, yani kalıplar davranışa hiçbir şey eklemedi.
- Bedel: dosya sayısı 1'den 7'ye, anlamlı satır 12'den 47'ye, ithal kapanışı 2'den 8'e, çağrı
  derinliği 4'ten 6'ya çıktı.
- Kalıplı sürümün en karmaşık gövdesi 6, kalıpsız sürümün tek gövdesi 3; tek işleyicili zincir
  karmaşıklığı düşürmek yerine yükseltti.
- Kazanç kalemleri sıfırlandı: tek koşumda denenen tarife 1'e karşı 1, kayıtlı gözlemci 1'e
  karşı 1, durum sayısı 2'ye karşı 2.
- Her kalıp, ayırdığı eksende ikinci bir örnek gerçekten varsa karşılığını verir; bedel peşin,
  kazanç vadelidir ve erken uygulanmış kalıbı geri almak uygulamaktan pahalıdır.

#### Sonraki Adım

Bu konuya kadarki kalıpların hepsi **nesne düzeyinde** çalıştı: bir sorumluluk alındı, ayrı bir
nesneye verildi ve bedeli dolaylılık basamağıyla ödendi. Eşik tablosunun tamamı bu ölçekte
kuruldu — bir algoritma, bir dinleyici, bir düğüm türü. Sıradaki konu ölçeği bir basamak yukarı
çıkarır. Soru artık bir sorumluluğun hangi nesneye verileceği değil, bir uygulamanın **katman
düzeninde** iş mantığının nereye yerleştiği ve kalıcılıkla arasındaki sınırın hangi kalıplarla
kurulduğudur. İlk soru en temel olanıdır: aynı iş kuralı bir senaryo yordamının içinde mi
yaşamalı, yoksa veriyi taşıyan nesnenin üzerinde mi. Sonraki ders bu iki düzenlemeyi kuralın kaç
ayrı yerde uygulandığını sayarak karşılaştırır ve ikinci düzenlemenin dosya, ad ve dolaylılık
bedelini aynı biçimde ölçer.

## Kurumsal Uygulama Kalıpları

İş mantığı ile kalıcılık arasındaki sınırın kalıplarla kurulması: işlem betiği ile alan modelinin iki düzenleme olarak karşılaştırılması, aktif kayıt ile veri eşleyicinin sonuçlarının tartılması, toplama erişiminin depo arkasına alınması ve işlem sınırının iş birimiyle yönetilmesi, aynı nesnenin tekilliğinin korunması, katmanlar arası veri taşıma, uygulama sınırının servis katmanıyla tanımlanması ve çalışma zamanında tip modelleme.

### İşlem Betiği ve Alan Modeli

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/islem-betigi-ve-alan-modeli

İş mantığının iki düzenlenişi: her senaryoyu baştan sona yürüten işlem betiği ile kuralı veriyi taşıyan nesnenin üzerine koyan alan modeli, aynı kuralın kaç yerde uygulandığının sayılması, iki düzenin aynı girdilerde eşitliğinin sınanması ve alan modelinin dosya, ad ve dolaylılık bedelinin tartılması.

Davranışsal kalıplar konusu, kalıp bedelinin kalıp kazancının altına düştüğü noktayı
gösterdi. Buraya kadarki bütün kalıplar **nesne düzeyinde** çalışıyordu: bir sorumluluk
alınıyor, ayrı bir nesneye veriliyor ve bedeli dolaylılık basamağıyla ödeniyordu.

Bu konu ölçeği bir basamak yukarı çıkarır. Soru artık bir sorumluluğun hangi nesneye
verileceği değil, bir uygulamanın **katman düzeninde** iş mantığının nereye yerleştiği ve
kalıcılıkla arasındaki sınırın hangi kalıplarla kurulduğudur. Bu kalıpların çoğu Veri Erişim
Katmanı kursunda kurulmuş ve orada başarım ölçüleriyle sınanmıştı. Burada aynı kalıplar
katalog düzeyinde ele alınır: her biri hangi problemi çözer, çözümü nedir, hangi sonuçları
doğurur ve **ne zaman uygulanmaz**.

#### Problem: Kural Kaç Yerde Durur

Gönderi ücretlendirme kitaplığında üç senaryo var: yeni gönderi kaydı açmak, var olan bir
gönderiye indirim eklemek, iade ücreti hesaplamak. Üçü de aynı iki kuralı kullanır:
indirim oranlarının toplamı bir tavanı aşamaz, net ücret asgari ücretin altına inemez.

Kuralın nereye yazıldığı iki düzenleme doğurur. **İşlem betiği** (transaction script) her
senaryoyu baştan sona yürüten bir yordam yazar; kural senaryonun içinde durur. **Alan
modeli** (domain model) kuralı veriyi taşıyan nesnenin üzerine koyar; senaryo yalnız sırayı
kurar.

```sh
mkdir -p betik alan
```

#### İşlem Betiği

```js
// betik/senaryolar.mjs — uc senaryo, her biri kurali kendi icinde uygular
const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const KATSAYI = { "34": 1, "06": 1.35, "65": 1.8 };
const ASGARI_UCRET = 3990;
const INDIRIM_TAVANI = 0.4;

const taban = (agirlik) => KADEME.find(([ust]) => agirlik <= ust)[1];
const katsayi = (postaKodu) => KATSAYI[postaKodu.slice(0, 2)] ?? 1.8;
const toplamOran = (indirimler) => indirimler.reduce((t, i) => t + i.oran, 0);

export function yeniGonderi(girdi) {
  const ham = Math.round(taban(girdi.agirlik) * katsayi(girdi.postaKodu));
  const oran = Math.min(toplamOran(girdi.indirimler), INDIRIM_TAVANI);
  return { ...girdi, ham, net: Math.max(Math.round(ham * (1 - oran)), ASGARI_UCRET) };
}

export function indirimEkle(gonderi, indirim) {
  const indirimler = [...gonderi.indirimler, indirim];
  const oran = Math.min(toplamOran(indirimler), INDIRIM_TAVANI);
  return { ...gonderi, indirimler, net: Math.max(Math.round(gonderi.ham * (1 - oran)), ASGARI_UCRET) };
}

export function iadeUcreti(gonderi) {
  const oran = Math.min(toplamOran(gonderi.indirimler), INDIRIM_TAVANI);
  return Math.round(gonderi.ham * 0.3 * (1 - oran));
}
```

Düzenin kazancı doğrudan okunabilirliğidir: bir senaryoyu anlamak için tek bir yordamı
baştan sona okumak yeterlidir, başka dosyaya gitmek gerekmez. Kaybı, kuralın senaryo sayısı
kadar tekrarlanmasıdır.

#### Alan Modeli

Aynı iş, kuralı nesnenin üzerine koyarak da yazılır.

```js
// alan/gonderi.mjs — kural nesnenin uzerinde, tek yerde
const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const KATSAYI = { "34": 1, "06": 1.35, "65": 1.8 };
const ASGARI_UCRET = 3990;
const INDIRIM_TAVANI = 0.4;

export class Gonderi {
  constructor(girdi) {
    this.agirlik = girdi.agirlik;
    this.postaKodu = girdi.postaKodu;
    this.indirimler = [...girdi.indirimler];
    this.ham = Math.round(this.#taban() * this.#katsayi());
  }
  #taban() { return KADEME.find(([ust]) => this.agirlik <= ust)[1]; }
  #katsayi() { return KATSAYI[this.postaKodu.slice(0, 2)] ?? 1.8; }
  indirimOrani() {
    return Math.min(this.indirimler.reduce((t, i) => t + i.oran, 0), INDIRIM_TAVANI);
  }
  net() { return Math.max(Math.round(this.ham * (1 - this.indirimOrani())), ASGARI_UCRET); }
  indirimEkle(indirim) { this.indirimler.push(indirim); return this; }
  iadeUcreti() { return Math.round(this.ham * 0.3 * (1 - this.indirimOrani())); }
}
```

```js
// alan/senaryolar.mjs — senaryolar yalniz sirayi kurar, kural nesnede kalir
import { Gonderi } from "./gonderi.mjs";

export const yeniGonderi = (girdi) => new Gonderi(girdi);
export const indirimEkle = (gonderi, indirim) => gonderi.indirimEkle(indirim);
export const iadeUcreti = (gonderi) => gonderi.iadeUcreti();
```

Senaryo dosyası üç satıra indi. İki kural artık `indirimOrani` ve `net` yöntemlerinin içinde,
her biri bir kez yazılmış durumda.

#### Kural Uygulama Noktasının Sayılması

İki düzenin farkı iddia edilmez, sayılır. Ölçü **kural uygulama noktası** sayısıdır: kuralın
adının, tanımlandığı satır dışında kaç ayrı satırda kullanıldığı.

```js
// kural-say.mjs — iki duzende kural uygulama noktasi, dosya ve disa acilan ad sayisi
import { readFileSync } from "node:fs";

const DUZEN = {
  "islem betigi": ["betik/senaryolar.mjs"],
  "alan modeli": ["alan/gonderi.mjs", "alan/senaryolar.mjs"],
};
const KURAL = { "indirim tavani": /INDIRIM_TAVANI/, "asgari ucret": /ASGARI_UCRET/ };

for (const [ad, dosyalar] of Object.entries(DUZEN)) {
  let satir = 0, disaAcilan = 0;
  const nokta = Object.fromEntries(Object.keys(KURAL).map((k) => [k, 0]));
  for (const dosya of dosyalar) {
    const satirlar = readFileSync(dosya, "utf8").split("\n");
    satir += satirlar.filter((s) => s.trim() !== "").length;
    for (const s of satirlar) {
      if (/^\s*export\b/.test(s)) disaAcilan += 1;
      if (/^\s*const [A-Z_]+ =/.test(s)) continue;
      for (const [k, kalip] of Object.entries(KURAL)) if (kalip.test(s)) nokta[k] += 1;
    }
  }
  console.log(`${ad}`);
  console.log(`  dosya = ${dosyalar.length}  satir = ${satir}  disa acilan ad = ${disaAcilan}`);
  for (const [k, n] of Object.entries(nokta)) console.log(`  ${k} uygulama noktasi = ${n}`);
}
```

```sh
node kural-say.mjs
```

```
islem betigi
  dosya = 1  satir = 22  disa acilan ad = 3
  indirim tavani uygulama noktasi = 3
  asgari ucret uygulama noktasi = 2
alan modeli
  dosya = 2  satir = 26  disa acilan ad = 4
  indirim tavani uygulama noktasi = 1
  asgari ucret uygulama noktasi = 1
```

Sayılar ödünleşimi tek satıra indiriyor. İndirim tavanı işlem betiğinde 3, alan modelinde 1
noktada uygulanıyor; asgari ücret 2 noktaya karşı 1 noktada. Kuralın biçimi değişirse —
tavan sabit bir oran olmaktan çıkıp müşteri türüne göre hesaplanan bir değere dönüşürse —
işlem betiğinde 3, alan modelinde 1 satır yeniden doğrulanmak zorundadır.

Bedel tarafı aynı çıktıda duruyor: alan modeli 1 dosya yerine 2 dosya, 22 satır yerine 26
satır ve 3 dışa açılan ad yerine 4 ad tutuyor. Kurala ulaşmak için okunması gereken dosya
sayısı da 1'den 2'ye çıkmış durumda.

#### İki Düzen Aynı Sonucu Veriyor mu

Bir düzenlemenin ötekinin yerine geçebileceği iddia edilmez, sınanır.

```js
// esdeger-mi.mjs — iki duzen ayni girdilerde ayni sonucu veriyor mu
import * as betik from "./betik/senaryolar.mjs";
import * as alan from "./alan/senaryolar.mjs";

const GIRDILER = [
  { agirlik: 0.6, postaKodu: "34710", indirimler: [] },
  { agirlik: 3, postaKodu: "06800", indirimler: [{ ad: "sozlesme", oran: 0.15 }] },
  { agirlik: 12, postaKodu: "65100", indirimler: [{ ad: "sozlesme", oran: 0.25 }, { ad: "hacim", oran: 0.3 }] },
  { agirlik: 0.4, postaKodu: "81600", indirimler: [{ ad: "hacim", oran: 0.5 }] },
];
const EK = { ad: "kampanya", oran: 0.1 };
const say = (n) => String(n).padStart(5);

let ayrilan = 0;
for (const girdi of GIRDILER) {
  const b = betik.yeniGonderi(girdi), a = alan.yeniGonderi(girdi);
  const ilk = [b.net, a.net()];
  const b2 = betik.indirimEkle(b, EK), a2 = alan.indirimEkle(a, EK);
  const son = [b2.net, a2.net()];
  const iade = [betik.iadeUcreti(b2), alan.iadeUcreti(a2)];
  if (ilk[0] !== ilk[1] || son[0] !== son[1] || iade[0] !== iade[1]) ayrilan += 1;
  console.log(`${String(girdi.agirlik).padStart(4)} kg ${girdi.postaKodu}  net ${say(ilk[0])}/${say(ilk[1])}  ek indirimli ${say(son[0])}/${say(son[1])}  iade ${say(iade[0])}/${say(iade[1])}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${GIRDILER.length}`);
```

```sh
node esdeger-mi.mjs
```

```
 0.6 kg 34710  net  3990/ 3990  ek indirimli  3990/ 3990  iade  1053/ 1053
   3 kg 06800  net  7344/ 7344  ek indirimli  6480/ 6480  iade  1944/ 1944
  12 kg 65100  net 12744/12744  ek indirimli 12744/12744  iade  3823/ 3823
 0.4 kg 81600  net  4212/ 4212  ek indirimli  4212/ 4212  iade  1264/ 1264
ayrilan sonuc = 0 / 4
```

Dört girdinin dördünde üç sonuç da aynı. İlk satırda asgari ücret, dördüncü satırda indirim
tavanı devreye giriyor; iki düzen de aynı sınırı aynı yerde uyguluyor. Seçim bir doğruluk
seçimi değil, bir değişim maliyeti seçimidir.

Sürümler arasında görünmeyen bir ayrım var: işlem betiği her adımda yeni bir kayıt
döndürüyor, alan modeli aynı nesneyi değiştiriyor. Bu ayrım, aynı gönderinin bellekte kaç
kopyası bulunduğu sorusunu doğurur ve kimlik haritası dersinin ölçtüğü şeydir.

#### Ne Zaman Uygulanmaz

Alan modelinin kazancı tekrarı kesmesinden gelir, dolayısıyla kazanç tekrar sayısıyla
büyür. Yukarıdaki ölçümde kazanç, tavan kuralında 3 noktadan 1 noktaya inmektir; bedel 1 ek
dosya, 1 ek dışa açılan ad ve 4 ek satırdır.

Tek senaryolu bir kitaplıkta aynı hesap tersine döner: uygulama noktası zaten 1'dir, kazanç
0 olur, bedel değişmez. Bir kuralı üç yerde yazmak zorunda kalmayan bir uygulamada alan
modeli, ödediği dolaylılığın karşılığını alamaz. Ayrım ölçütü senaryo sayısı da değildir —
aynı kurala dokunan senaryo sayısıdır. Beş senaryodan biri kuralı kullanıyorsa işlem betiği
hâlâ ucuzdur.

İkinci karşı durum, kuralların veriden değil dış sistemlerden türediği işlerdir. Alan modeli
nesnenin kendi verisinden karar üretir; kararın girdisi her seferinde bir dış çağrıysa nesne
yalnız veri taşır, kural yine senaryoda kalır. Bu duruma **anemik alan modeli** (anemic
domain model) adı verilir: sınıf vardır, davranış yoktur, dolayısıyla bedel ödenir ama kazanç
alınmaz.

#### Özet

- İşlem betiği her senaryoyu tek yordamda yürütür ve kuralı senaryonun içine yazar; alan
  modeli kuralı veriyi taşıyan nesneye taşır, senaryoya yalnız sırayı bırakır.
- Kural uygulama noktası sayıldığında indirim tavanı işlem betiğinde 3, alan modelinde 1
  noktada, asgari ücret 2 noktaya karşı 1 noktada uygulanıyordu.
- Alan modelinin bedeli aynı ölçümde göründü: 1 dosya yerine 2 dosya, 22 satır yerine 26
  satır, 3 dışa açılan ad yerine 4 ad ve kurala ulaşmak için 2 dosya okuma.
- Dört girdinin dördünde iki düzen aynı net ücreti ve aynı iade ücretini verdi; seçim
  doğruluk değil değişim maliyeti seçimidir.
- Kazanç aynı kurala dokunan senaryo sayısıyla büyür; tek senaryoda kazanç sıfırlanır, bedel
  kalır. Davranışı olmayan sınıflar anemik alan modeli üretir.

#### Sonraki Adım

Bu dersteki iki düzen de gönderiyi bellekte tuttu; hiçbir yere yazmadı. Gönderi saklanmaya
başladığında nesne ile saklanan kayıt arasında bir çevrim doğar ve bu çevrimin nereye
konulacağı ikinci bir kalıp çiftini ortaya çıkarır: çevrimi nesnenin kendi üzerine koymak mı,
ayrı bir eşleyiciye vermek mi. Sonraki ders bu iki kalıbı aynı gönderi üzerinde kurar, alan
modülünün kalıcılık hakkında bilmek zorunda olduğu ad sayısını ve ithal kapanışının boyutunu
sayar, ikinci bir saklama biçimi eklendiğinde dokunulan dosya sayısını karşılaştırır.

### Aktif Kayıt ve Veri Eşleyici

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/aktif-kayit-ve-veri-esleyici

Nesne ile saklanan kayıt arasındaki çevrimin nereye konulacağı: çevrimi nesnenin üzerine koyan aktif kayıt ile ayrı bir eşleyiciye veren veri eşleyicinin karşılaştırılması, alan modülündeki kalıcılık izinin ve ithal kapanışının sayılması, iki sürümün gidiş-dönüşte eşitliği ve ikinci bir saklama biçiminin alan modülüne dokunmadan eklenmesi.

Önceki ders iş mantığının iki düzenlenişini karşılaştırdı ve ikisini de bellekte bıraktı.
Gönderi saklanmaya başladığı anda nesne ile saklanan kayıt arasında bir **çevrim** doğar:
nesnenin alanları bir satıra, satır geri nesneye dönüştürülmelidir. Bu çevrimin nereye
konulacağı iki kalıp üretir. **Aktif kayıt** (active record) çevrimi ve saklama erişimini
nesnenin kendi üzerine koyar; nesne kendi satırını okur ve yazar. **Veri eşleyici** (data
mapper) çevrimi ayrı bir nesneye verir; alan nesnesi saklandığından habersiz kalır.

Bu iki kalıp Veri Erişim Katmanı kursunda kurulmuş ve orada üretilen sorgu sayısıyla
ölçülmüştü. Burada ölçü bir tasarım ölçüsüdür: alan modülünün kalıcılık hakkında bilmek
zorunda olduğu ad sayısı, ithal kapanışının boyutu ve saklama biçimi değiştiğinde dokunulan
dosya sayısı. Bağlantı havuzu, sorgu planı ve dizin ayrıntısı o kursa aittir; burada saklama
yerine sade bir bellek içi depo konur.

```sh
mkdir -p aktif esleyici
```

```js
// bellek-depo.mjs — sade nesne deposu: tablo adi ve kimlige gore satir tutar
const TABLOLAR = new Map();
const tablo = (ad) => TABLOLAR.get(ad) ?? TABLOLAR.set(ad, new Map()).get(ad);

export const depo = {
  oku: (ad, id) => tablo(ad).get(id) ?? null,
  yaz: (ad, satir) => { tablo(ad).set(satir.id, { ...satir }); },
  say: (ad) => tablo(ad).size,
};
```

#### Aktif Kayıt

Kalıbın çözümü tek cümledir: satırın alanlarını taşıyan sınıf, o satırı okuyan ve yazan
yöntemleri de taşır.

```js
// aktif/gonderi.mjs — nesne kendi satirini okur ve yazar
import { depo } from "../bellek-depo.mjs";

const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const ASGARI_UCRET = 3990;

export class Gonderi {
  constructor(satir) { Object.assign(this, satir); }
  net() {
    const taban = KADEME.find(([ust]) => this.agirlik <= ust)[1];
    return Math.max(Math.round(taban * this.katsayi * (1 - this.indirim)), ASGARI_UCRET);
  }
  kaydet() {
    depo.yaz("gonderi", { id: this.id, agirlik: this.agirlik, katsayi: this.katsayi, indirim: this.indirim });
    return this;
  }
  static bul(id) {
    const satir = depo.oku("gonderi", id);
    return satir === null ? null : new Gonderi(satir);
  }
}
```

Kazanç dosya sayısındadır: bir kavram, bir sınıf, bir dosya. Kaybı, kural ile kalıcılığın
aynı dosyada durmasıdır.

#### Veri Eşleyici

Aynı iş iki dosyaya bölünür. Alan nesnesi yalnız kuralı taşır.

```js
// esleyici/gonderi.mjs — alan nesnesi: yalniz kural, kalicilik adi yok
const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const ASGARI_UCRET = 3990;

export class Gonderi {
  constructor({ id, agirlik, katsayi, indirim }) {
    Object.assign(this, { id, agirlik, katsayi, indirim });
  }
  net() {
    const taban = KADEME.find(([ust]) => this.agirlik <= ust)[1];
    return Math.max(Math.round(taban * this.katsayi * (1 - this.indirim)), ASGARI_UCRET);
  }
}
```

```js
// esleyici/gonderi-esleyici.mjs — cevrimi ve depo erisimini tasiyan esleyici
import { Gonderi } from "./gonderi.mjs";
import { depo } from "../bellek-depo.mjs";

export const gonderiEsleyici = {
  nesneye: (satir) => new Gonderi(satir),
  satira: (g) => ({ id: g.id, agirlik: g.agirlik, katsayi: g.katsayi, indirim: g.indirim }),
  bul(id) {
    const satir = depo.oku("gonderi", id);
    return satir === null ? null : this.nesneye(satir);
  },
  kaydet(g) { depo.yaz("gonderi", this.satira(g)); return g; },
};
```

Bağımlılık oku yön değiştirdi: alan nesnesi eşleyiciyi bilmiyor, eşleyici alan nesnesini
biliyor.

#### İzin ve Kapanışın Sayılması

Ölçü üç sayıdır. Birincisi **kalıcılık izi**: alan modülünde depo adının, tablo adının ve
satır sözcüğünün kaç kez geçtiği. İkincisi alan modülünün **ithal kapanışı**: o modülü
yüklemek için yüklenmesi gereken başka modül sayısı. Üçüncüsü hem kuralı hem depo erişimini
taşıyan dosya sayısı.

```js
// iz-say.mjs — alan modulunde kalicilik izi, ithal kapanisi ve depoya baglanan dosya sayisi
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";

const IZ = { "depo adi": /\bdepo\b/g, "tablo adi": /"gonderi"/g, "satir sozcugu": /\bsatir\b/g };
const ITHAL = /from "([^"]+)"/g;

function kapanis(baslangic) {
  const gorulen = new Set(), yigit = [normalize(baslangic)];
  while (yigit.length > 0) {
    const dosya = yigit.pop();
    if (gorulen.has(dosya)) continue;
    gorulen.add(dosya);
    for (const [, yol] of readFileSync(dosya, "utf8").matchAll(ITHAL)) {
      if (yol.startsWith(".")) yigit.push(normalize(join(dirname(dosya), yol)));
    }
  }
  gorulen.delete(normalize(baslangic));
  return gorulen;
}

const SURUM = {
  "aktif kayit": { alan: "aktif/gonderi.mjs", dosyalar: ["aktif/gonderi.mjs"] },
  "veri esleyici": {
    alan: "esleyici/gonderi.mjs",
    dosyalar: ["esleyici/gonderi.mjs", "esleyici/gonderi-esleyici.mjs"],
  },
};

for (const [ad, s] of Object.entries(SURUM)) {
  const metin = readFileSync(s.alan, "utf8");
  const izler = Object.entries(IZ).map(([k, r]) => `${k} = ${[...metin.matchAll(r)].length}`);
  const baglanan = s.dosyalar.filter((d) => readFileSync(d, "utf8").includes("bellek-depo.mjs"));
  const kuralDosyasi = s.dosyalar.filter((d) => readFileSync(d, "utf8").includes("ASGARI_UCRET"));
  const ikisi = kuralDosyasi.filter((d) => baglanan.includes(d));
  console.log(ad);
  console.log(`  alan modulu (${s.alan}) izleri: ${izler.join(", ")}`);
  console.log(`  alan modulu ithal kapanisi = ${kapanis(s.alan).size}`);
  console.log(`  depoya baglanan dosya = ${baglanan.length} / ${s.dosyalar.length} (${baglanan.join(", ")})`);
  console.log(`  hem kural hem depo tasiyan dosya = ${ikisi.length}`);
}
```

```sh
node iz-say.mjs
```

```
aktif kayit
  alan modulu (aktif/gonderi.mjs) izleri: depo adi = 4, tablo adi = 2, satir sozcugu = 5
  alan modulu ithal kapanisi = 1
  depoya baglanan dosya = 1 / 1 (aktif/gonderi.mjs)
  hem kural hem depo tasiyan dosya = 1
veri esleyici
  alan modulu (esleyici/gonderi.mjs) izleri: depo adi = 0, tablo adi = 0, satir sozcugu = 0
  alan modulu ithal kapanisi = 0
  depoya baglanan dosya = 1 / 2 (esleyici/gonderi-esleyici.mjs)
  hem kural hem depo tasiyan dosya = 0
```

Aktif kayıtta alan modülü depo sözcüğünü 4, tablo adını 2, satır sözcüğünü 5 kez taşıyor;
eşleyici sürümünde üçü de 0. İthal kapanışı 1'den 0'a iniyor: kural dosyasını yüklemek için
depo modülünün yüklenmesi gerekmiyor. Son satırlar farkın adını veriyor: aktif kayıtta kural
ile depo erişimi aynı dosyada, eşleyici sürümünde ayrı dosyalarda.

#### Gidiş-Dönüş Eşitliği

İki kalıp aynı işi yapıyor mu, sınanır.

```js
// gidis-donus.mjs — iki surum ayni satirlari yazip okuyunca ayni neti veriyor mu
import { Gonderi as AktifGonderi } from "./aktif/gonderi.mjs";
import { Gonderi as AlanGonderi } from "./esleyici/gonderi.mjs";
import { gonderiEsleyici } from "./esleyici/gonderi-esleyici.mjs";

const SATIRLAR = [
  { id: "G1", agirlik: 0.6, katsayi: 1, indirim: 0 },
  { id: "G2", agirlik: 3, katsayi: 1.35, indirim: 0.1 },
  { id: "G3", agirlik: 12, katsayi: 1.8, indirim: 0.4 },
];

let ayrilan = 0;
for (const satir of SATIRLAR) {
  new AktifGonderi(satir).kaydet();
  const a = AktifGonderi.bul(satir.id).net();
  gonderiEsleyici.kaydet(new AlanGonderi(satir));
  const e = gonderiEsleyici.bul(satir.id).net();
  if (a !== e) ayrilan += 1;
  console.log(`${satir.id}  aktif kayit ${String(a).padStart(6)}   veri esleyici ${String(e).padStart(6)}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${SATIRLAR.length}`);
```

```sh
node gidis-donus.mjs
```

```
G1  aktif kayit   3990   veri esleyici   3990
G2  aktif kayit   7776   veri esleyici   7776
G3  aktif kayit  12744   veri esleyici  12744
ayrilan sonuc = 0 / 3
```

Üç satırın üçünde sonuç aynı. Fark doğrulukta değil, saklama kararının nereye yazıldığında.

#### İkinci Saklama Biçimi

Kalıcılık ayrımının bedelini ödemenin karşılığı burada görünür. İkinci bir depo eklenir:
yazmaları bir kütüğe ekleyen, okumada son satırı veren bir depo.

```js
// kutuk-depo.mjs — ikinci saklama bicimi: her yazma bir kutuk satiri, okuma son satiri verir
const KUTUK = [];

export const kutukDepo = {
  yaz: (ad, satir) => { KUTUK.push({ ad, satir: { ...satir } }); },
  oku: (ad, id) => [...KUTUK].reverse().find((k) => k.ad === ad && k.satir.id === id)?.satir ?? null,
  uzunluk: () => KUTUK.length,
};
```

```js
// esleyici/kutuk-esleyici.mjs — ayni alan nesnesi, ikinci depoya yazan ikinci esleyici
import { Gonderi } from "./gonderi.mjs";
import { kutukDepo } from "../kutuk-depo.mjs";

export const kutukEsleyici = {
  satira: (g) => ({ id: g.id, agirlik: g.agirlik, katsayi: g.katsayi, indirim: g.indirim }),
  bul(id) {
    const satir = kutukDepo.oku("gonderi", id);
    return satir === null ? null : new Gonderi(satir);
  },
  kaydet(g) { kutukDepo.yaz("gonderi", this.satira(g)); return g; },
};
```

```js
// ikinci-depo.mjs — alan nesnesi degismeden ikinci depoya yazilabiliyor mu
import { readFileSync } from "node:fs";
import { Gonderi } from "./esleyici/gonderi.mjs";
import { gonderiEsleyici } from "./esleyici/gonderi-esleyici.mjs";
import { kutukEsleyici } from "./esleyici/kutuk-esleyici.mjs";
import { kutukDepo } from "./kutuk-depo.mjs";

const SATIR = { id: "G2", agirlik: 3, katsayi: 1.35, indirim: 0.1 };
const g = new Gonderi(SATIR);
gonderiEsleyici.kaydet(g);
kutukEsleyici.kaydet(g);
kutukEsleyici.kaydet(new Gonderi({ ...SATIR, indirim: 0.2 }));

console.log(`bellek deposundan net = ${gonderiEsleyici.bul("G2").net()}`);
console.log(`kutuk deposundan net  = ${kutukEsleyici.bul("G2").net()}`);
console.log(`kutuk satiri = ${kutukDepo.uzunluk()}`);
const alan = readFileSync("esleyici/gonderi.mjs", "utf8");
console.log(`alan modulunde depo adi = ${[...alan.matchAll(/\bdepo\b/gi)].length}`);
```

```sh
node ikinci-depo.mjs
```

```
bellek deposundan net = 7776
kutuk deposundan net  = 6912
kutuk satiri = 2
alan modulunde depo adi = 0
```

Aynı alan nesnesi iki depoya yazıldı ve ikisinden de okundu; alan modülünde depo adı hâlâ 0.
İkinci saklama biçimini eklemek için düzenlenen var olan dosya sayısı 0, eklenen dosya sayısı
2 oldu. Aktif kayıtta aynı gereksinim `aktif/gonderi.mjs` dosyasının düzenlenmesini gerektirir
ve o dosya kuralı taşıdığı için kural da yeniden doğrulanmak zorunda kalır.

#### Ne Zaman Uygulanmaz

Veri eşleyicinin bedeli sayılabilir. Aktif kayıt sürümü 1 dosya tutuyor, eşleyici sürümü depo
ile birlikte 3 dosya. Bir yazma aktif kayıtta 1 çağrıdır, eşleyicide 2 çağrıya bölünür: önce
satıra çevirme, sonra yazma. Kaydı okuyup nesneyi elde etmek için izlenmesi gereken dosya
sayısı 1'den 2'ye çıkar.

Satır ile nesne arasında bire bir örtüşme varsa ve kural tek satırlık bir hesaptan
oluşuyorsa, eşleyicinin çevirdiği hiçbir şey yoktur: `satira` yöntemi alanları aynı adlarla
kopyalar. Bu durumda ödenen 2 ek dosya ve 1 ek dolaylılık basamağı karşılıksızdır; aktif kayıt
daha az parçayla aynı işi yapar.

Aktif kayıt ise nesne modeli ile satır düzeni ayrıştığında pahalanır. Kural birden çok satırın
birleşiminden karar üretiyorsa nesne artık bir satırı temsil etmez ve kaydetme yönteminin
hangi satırları yazacağı belirsizleşir. Bu ayrışmanın adı Veri Erişim Katmanı kursunda
konulmuştu: nesne–ilişki uyumsuzluğu. Ayrışma büyüdükçe kalıcılık kodu kural dosyasının
içinde şişer ve ölçüm ilk çıktıdaki iz sayılarını yukarı taşır.

#### Özet

- Aktif kayıt çevrimi ve saklama erişimini alan nesnesinin üzerine koyar; veri eşleyici ayrı
  bir nesneye verir ve bağımlılık okunu alan nesnesinden dışarı çevirir.
- Alan modülünde kalıcılık izi aktif kayıtta depo adı 4, tablo adı 2, satır sözcüğü 5 iken
  eşleyici sürümünde üçü de 0 çıktı; ithal kapanışı 1'den 0'a indi.
- Üç satırın üçünde iki sürüm aynı net ücreti verdi; kalıp seçimi doğruluğu değil, saklama
  kararının hangi dosyada durduğunu değiştirir.
- İkinci saklama biçimi eşleyici sürümünde 0 var olan dosya düzenlenerek ve 2 dosya eklenerek
  bağlandı; alan modülünde depo adı 0 kaldı.
- Eşleyicinin bedeli 2 ek dosya, yazma başına 1 ek çağrı ve okunması gereken 1 ek dosyadır;
  satır ile nesne bire bir örtüşüyorsa bu bedel karşılıksız kalır.

#### Sonraki Adım

Bu dersin eşleyicisi tek bir nesneyi tek bir satıra çeviriyor. Gerçek bir senaryo tek nesneye
dokunmaz: bir gönderi kaydedilirken rotası ve indirim satırları da yazılır, gönderi satırındaki
indirim toplamı da yenilenir. Çağıran taraf hangi nesneleri hangi sırayla
kaydedeceğini bilmek zorunda kalırsa kalıcılık kararı yeniden yukarı sızar. Sonraki ders
erişimi alan diliyle veren depo kalıbını ve değişen satırları tek bir yazma noktasında
toplayan iş birimini kurar, elle kaydetmeyle karşılaştırıp uygulama katmanındaki yazma
noktalarını ve aynı senaryonun üç düzende ürettiği yazma çağrısı sayısını sayar.

### Depo ve İş Birimi

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/depo-ve-is-birimi

Bir arada tutulan nesne kümesinin erişim ve yazma sınırı: elle eşleyici çağıran uygulama katmanı ile toplamayı alan diliyle veren depo ve değişen satırları tek noktada toplayan iş birimi, üç düzende yazma çağrısı, yazma noktası ve arama çağrısı sayılarının karşılaştırılması.

Önceki dersin eşleyicisi tek bir nesneyi tek bir satıra çeviriyordu. Gerçek bir senaryo tek
nesneye dokunmaz: bir gönderi kaydedilirken rotası ve indirim satırları da yazılır, gönderi
satırındaki indirim toplamı da yenilenir. Bu üç satır birbirinden bağımsız değildir; bir
arada anlam taşıyan bir küme oluşturur ve bu kümeye **toplama** (aggregate) adı verilir.

Toplamanın varlığı iki soru doğurur. Birincisi erişim sorusu: çağıran taraf toplamanın
parçalarını tek tek mi arayacak, bütününü mü isteyecek. İkincisi yazma sorusu: hangi
satırların hangi sırada yazılacağını kim bilecek. Birinci soruyu **depo** (repository),
ikincisini **iş birimi** (unit of work) yanıtlar. İki kalıp da Veri Erişim Katmanı kursunda
kurulmuştu; işlem sınırı, yalıtım düzeyi ve kilitleme davranışı orada ölçüldü. Burada ölçü
bir tasarım ölçüsüdür: uygulama katmanının taşıdığı kalıcılık adı sayısı, yazma noktası
sayısı ve aynı senaryonun ürettiği yazma çağrısı sayısı.

```sh
mkdir -p esleyici elle depo
```

```js
// bellek-depo.mjs — sade nesne deposu; yapilan yazmalari da sayar
const TABLOLAR = new Map();
let yazma = 0;
const tablo = (ad) => TABLOLAR.get(ad) ?? TABLOLAR.set(ad, new Map()).get(ad);

export const depo = {
  oku: (ad, id) => tablo(ad).get(id) ?? null,
  yaz: (ad, satir) => { yazma += 1; tablo(ad).set(satir.id, { ...satir }); },
  yazmaSayisi: () => yazma,
  sifirla: () => { yazma = 0; },
};
```

```js
// esleyici/tablo-esleyici.mjs — tablo basina ayni cevrimi kuran kucuk esleyici
import { depo } from "../bellek-depo.mjs";

export const esleyici = (tablo) => ({
  bul: (id) => depo.oku(tablo, id),
  kaydet: (satir) => depo.yaz(tablo, satir),
});
```

#### Elle Kaydetme

İlk düzende uygulama katmanı üç eşleyiciyi kendisi kullanır. Her adımda hangi tabloların
değiştiğine de kendisi karar verir.

```js
// elle/senaryo.mjs — uygulama katmani hangi tabloyu hangi sirada yazacagini kendi bilir
import { esleyici } from "../esleyici/tablo-esleyici.mjs";

const gonderiE = esleyici("gonderi"), rotaE = esleyici("rota"), indirimE = esleyici("indirim");
const ozet = (t) => ({ ...t.gonderi, indirimToplami: t.indirimler.reduce((s, i) => s + i.oran, 0) });

export function olustur(toplama) {
  gonderiE.kaydet(ozet(toplama));
  rotaE.kaydet(toplama.rota);
  for (const i of toplama.indirimler) indirimE.kaydet(i);
  return toplama;
}

export function aktarmaEkle(toplama, nokta) {
  toplama.rota.aktarma.push(nokta);
  rotaE.kaydet(toplama.rota);
  return toplama;
}

export function indirimEkle(toplama, indirim) {
  toplama.indirimler.push(indirim);
  indirimE.kaydet(indirim);
  gonderiE.kaydet(ozet(toplama));
  return toplama;
}

export function topla(id) {
  const gonderi = gonderiE.bul(id);
  const rota = rotaE.bul(`R-${id}`);
  const indirimler = [indirimE.bul(`I-${id}`)].filter((x) => x !== null);
  return { gonderi, rota, indirimler };
}
```

Bu dosyada iki tür bilgi var: senaryonun sırası ve kalıcılığın yapısı. İkinci tür bilgi
üçüncü kez yazılan bir kararı içeriyor — indirim eklendiğinde gönderi satırının da
yenileneceği kararı.

#### Depo

Depo, çağıran tarafa toplamanın bütününü verir. Yazma işini ise doğrudan yapmaz, kendisine
verilen bir **yazıcıya** devreder; bu, iş biriminin sonradan araya girmesini mümkün kılar.

```js
// depo/gonderi-deposu.mjs — toplamanin tamamini alan diliyle veren depo
import { depo } from "../bellek-depo.mjs";

const ozet = (t) => ({ ...t.gonderi, indirimToplami: t.indirimler.reduce((s, i) => s + i.oran, 0) });

export const gonderiDeposu = (yazici) => ({
  bul(id) {
    const gonderi = depo.oku("gonderi", id);
    if (gonderi === null) return null;
    const indirimler = [depo.oku("indirim", `I-${id}`)].filter((x) => x !== null);
    return { gonderi, rota: depo.oku("rota", `R-${id}`), indirimler };
  },
  kaydet(toplama) {
    yazici("gonderi", ozet(toplama));
    yazici("rota", toplama.rota);
    for (const i of toplama.indirimler) yazici("indirim", i);
    return toplama;
  },
});
```

```js
// depo/senaryo.mjs — ayni senaryo: uygulama katmani yalniz toplamayi kaydeder
import { gonderiDeposu } from "./gonderi-deposu.mjs";

export const senaryo = (yazici) => {
  const kaynak = gonderiDeposu(yazici);
  return {
    olustur: (toplama) => kaynak.kaydet(toplama),
    aktarmaEkle(toplama, nokta) {
      toplama.rota.aktarma.push(nokta);
      return kaynak.kaydet(toplama);
    },
    indirimEkle(toplama, indirim) {
      toplama.indirimler.push(indirim);
      return kaynak.kaydet(toplama);
    },
    topla: (id) => kaynak.bul(id),
  };
};
```

Senaryo dosyasında tablo adı kalmadı ve her adım tek bir kaydetme çağrısıyla bitiyor.
Karşılığında depo her kaydetmede toplamanın bütününü yazıyor: değişmeyen satırları da.

#### İş Birimi

İş birimi bu iki uçtan üçüncü bir yol açar. Yazmaları anında yapmak yerine hangi satırların
kirlendiğini kaydeder, sonunda her satırı bir kez yazar.

```js
// depo/is-birimi.mjs — degisen satirlari izler, yazmayi tek noktada toplar
import { depo } from "../bellek-depo.mjs";

export function isBirimi() {
  const kirli = new Map();
  return {
    kirlet: (tablo, satir) => { kirli.set(`${tablo}:${satir.id}`, { tablo, satir }); },
    izlenen: () => kirli.size,
    tamamla() {
      for (const { tablo, satir } of kirli.values()) depo.yaz(tablo, satir);
      const n = kirli.size;
      kirli.clear();
      return n;
    },
  };
}
```

Anahtar `tablo:kimlik` çiftidir; aynı satır üç kez kirlense de haritada bir kez durur. Deponun
yazıcısı olarak `kirlet` verildiğinde depo kodu hiç değişmeden yazma davranışı değişir.

#### Üç Düzenin Sayılması

Aynı üç adım — toplama oluştur, rotaya aktarma noktası ekle, indirim ekle — üç düzende
koşturulur ve iki tür sayı çıkarılır: koşumda yapılan yazma çağrısı, kaynak dosyalarda duran
tablo adı, yazma noktası ve arama çağrısı sayısı.

```js
// yazma-say.mjs — ayni uc adim uc duzende: yazma cagrisi, yazma noktasi ve arama cagrisi
import { readFileSync } from "node:fs";
import { depo } from "./bellek-depo.mjs";
import * as elle from "./elle/senaryo.mjs";
import { senaryo } from "./depo/senaryo.mjs";
import { isBirimi } from "./depo/is-birimi.mjs";

const yeni = (id) => ({
  gonderi: { id, agirlik: 3, katsayi: 1.35 },
  rota: { id: `R-${id}`, gonderiId: id, aktarma: ["34"] },
  indirimler: [],
});
const indirim = (id) => ({ id: `I-${id}`, gonderiId: id, ad: "sozlesme", oran: 0.15 });
const okuyucu = senaryo(() => {});

function olc(ad, id, adimlar) {
  depo.sifirla();
  const ek = adimlar(id);
  const t = okuyucu.topla(id);
  console.log(`${ad}: yazma cagrisi = ${depo.yazmaSayisi()}, indirim toplami = ${t.gonderi.indirimToplami}${ek}`);
}

olc("elle esleyici       ", "G1", (id) => {
  elle.indirimEkle(elle.aktarmaEkle(elle.olustur(yeni(id)), "06"), indirim(id));
  return "";
});

olc("depo, dogrudan yazma", "G2", (id) => {
  const s = senaryo(depo.yaz);
  s.indirimEkle(s.aktarmaEkle(s.olustur(yeni(id)), "06"), indirim(id));
  return "";
});

olc("depo ve is birimi   ", "G3", (id) => {
  const birim = isBirimi();
  const s = senaryo(birim.kirlet);
  s.indirimEkle(s.aktarmaEkle(s.olustur(yeni(id)), "06"), indirim(id));
  return `, izlenen kirli satir = ${birim.izlenen()}, tamamla() yazdi = ${birim.tamamla()}`;
});

const SAY = { "tablo adi": /"(gonderi|rota|indirim)"/g, "yazma noktasi": /\b(kaydet|kirlet)\(/g, "arama cagrisi": /\.bul\(/g };
for (const dosya of ["elle/senaryo.mjs", "depo/senaryo.mjs"]) {
  const m = readFileSync(dosya, "utf8");
  const alanlar = Object.entries(SAY).map(([k, r]) => `${k} = ${[...m.matchAll(r)].length}`);
  console.log(`${dosya}: ${alanlar.join(", ")}`);
}
```

```sh
node yazma-say.mjs
```

```
elle esleyici       : yazma cagrisi = 5, indirim toplami = 0.15
depo, dogrudan yazma: yazma cagrisi = 7, indirim toplami = 0.15
depo ve is birimi   : yazma cagrisi = 3, indirim toplami = 0.15, izlenen kirli satir = 3, tamamla() yazdi = 3
elle/senaryo.mjs: tablo adi = 3, yazma noktasi = 6, arama cagrisi = 3
depo/senaryo.mjs: tablo adi = 0, yazma noktasi = 3, arama cagrisi = 1
```

Üç düzen aynı indirim toplamını okudu: davranış değişmedi. Değişen sayılar şunlar.

Uygulama katmanının taşıdığı kalıcılık bilgisi depoyla birlikte silindi: tablo adı 3'ten 0'a,
yazma noktası 6'dan 3'e, arama çağrısı 3'ten 1'e indi. Toplamanın parçalarını bulmak için
uygulama katmanının bildiği kimlik biçimi — rotanın `R-` önekiyle türetildiği bilgisi —
artık deponun içinde duruyor.

Yazma çağrısı sayısı beklenenin tersine hareket etti. Elle düzen 5, depo 7 yazma üretti;
çünkü depo her adımda toplamanın tamamını yazıyor, değişmemiş rota satırını da. İş birimi
eklendiğinde sayı 3'e indi: üç kirli satır, üç yazma. Depo tek yazma noktası kazandırır, iş
birimi o noktanın kaç kez tetiklendiğini düşürür. İkisi ayrı problem çözer ve birlikte
kullanıldıklarında sayı hem tek noktaya iner hem en aza düşer.

#### Bedel ve Ne Zaman Uygulanmaz

Depo 2 dosya ekler ve toplamaya erişim için 1 dolaylılık basamağı koyar; iş birimi 1 dosya
daha ekler ve yazmanın ne zaman gerçekleştiğini çağrı noktasından kaldırır. Bu ikinci bedel
sayının içinde görünmez: `kaydet` çağrıldığında hiçbir şey yazılmadığı için hata `tamamla`
satırında ortaya çıkar ve izlenmesi gereken çağrı zinciri iki basamak uzar.

Depo, toplaması olmayan bir kayıt için karşılıksızdır. Tek tablodan oluşan bir arama kaydında
depo yalnız eşleyiciyi başka bir adla sarar; ölçüm o durumda tablo adı 3'ten 0'a değil,
1'den 0'a iner ve karşılığında 1 dosya ödenir. Aynı şekilde raporlama okumaları toplama
sınırına uymaz: bir depo arayüzü altı farklı süzgeç kombinasyonunu karşılamak için altı
yöntemle büyür. Bu sınır Veri Erişim Katmanı kursundaki sorgu nesneleri ve belirtimler
dersinin konusudur.

İş birimi ise tek satır yazan senaryolarda ölçüyü iyileştirmez: kirli satır sayısı 1 olur,
yazma sayısı zaten 1'dir, kazanç 0 kalır. Kazancı satır sayısıyla ve aynı satıra dokunan
adım sayısıyla büyür.

#### Özet

- Toplama, bir arada anlam taşıyan satır kümesidir; depo bu kümeye erişimi alan diliyle
  verir, iş birimi kümedeki değişimlerin ne zaman yazılacağını yönetir.
- Depo uygulandığında uygulama katmanında tablo adı 3'ten 0'a, yazma noktası 6'dan 3'e, arama
  çağrısı 3'ten 1'e indi; kimlik türetme kuralı deponun içine geçti.
- Aynı üç adım elle düzende 5, depoyla 7, depo ve iş birimiyle 3 yazma çağrısı üretti; depo
  tek yazma noktası kazandırır, iş birimi tetiklenme sayısını düşürür.
- Üç düzen aynı indirim toplamını okudu; kalıplar davranışı değiştirmedi, kalıcılık kararının
  hangi dosyada durduğunu ve kaç kez tetiklendiğini değiştirdi.
- Toplaması olmayan tek tablolu kayıtta depo bir sarmalayıcıya, tek satır yazan senaryoda iş
  birimi kazançsız bir dolaylılığa dönüşür.

#### Sonraki Adım

İş birimi kirli satırları `tablo:kimlik` anahtarıyla topladı; aynı satır üç kez kirlense de
bir kez yazıldı. Bu, yazma tarafında çözülmüş bir tekillik sorunudur. Okuma tarafında aynı
sorun çözülmemiş durumda: aynı gönderi kimliği iki kez arandığında iki ayrı nesne doğar,
biri değiştirilir, öteki eski değerlerle elde kalır ve sonra yazılırsa ilkini ezer. Sonraki
ders aynı kimliğin tek nesneyle temsil edilmesini sağlayan kimlik haritasını kurar, üretilen
ayrı örnek sayısını ve kimliğe göre eşitlik yazmak zorunda kalınan yer sayısını sayar.

### Kimlik Haritası

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/kimlik-haritasi

Aynı kimliğin tek nesneyle temsil edilmesi: haritasız ve haritalı koşumda üretilen ayrı örnek sayısının, iki kod yolunun aynı kaydı değiştirmesiyle kaybolan alan sayısının ve haritanın bellekte tuttuğu nesne sayısının ölçülmesi, haritanın yaşam süresi uzadığında ortaya çıkan bayat okumanın gösterilmesi.

İş birimi kirli satırları `tablo:kimlik` anahtarıyla topladı ve aynı satır üç kez kirlense de
bir kez yazıldı. Bu, yazma tarafında çözülmüş bir tekillik sorunudur. Okuma tarafında aynı
sorun açık duruyor: aynı gönderi kimliği iki kez arandığında iki ayrı nesne doğar. Bir kod
yolu birinci nesnenin indirimini değiştirir, ikinci kod yolu ikinci nesnenin rotasına aktarma
ekler; ikisi de kaydedildiğinde son yazan öteki değişikliği siler.

**Kimlik haritası** (identity map) bu soruna tek cümlelik bir çözüm koyar: bir kimlik için
üretilen nesne saklanır, aynı kimlik yeniden arandığında yeni nesne üretilmez, saklanan
döndürülür. Kalıp Veri Erişim Katmanı kursunda üretilen sorgu sayısı üzerinden ölçülmüştü.
Burada ölçü nesne düzeyindedir: kaç ayrı örnek doğdu, kaç alan kayboldu, harita bellekte kaç
nesne tuttu.

#### Problem: Aynı Kimlik, İki Nesne

Deponun okuma davranışı sorunun kaynağıdır. Bir depo satırın kendisini değil kopyasını verir;
aksi hâlde çağıran taraf saklanan satırı doğrudan değiştirebilirdi.

```js
// bellek-depo.mjs — her okumada satirin yeni bir kopyasini veren sade depo
const TABLOLAR = new Map();
const tablo = (ad) => TABLOLAR.get(ad) ?? TABLOLAR.set(ad, new Map()).get(ad);

export const depo = {
  oku: (ad, id) => structuredClone(tablo(ad).get(id) ?? null),
  yaz: (ad, satir) => { tablo(ad).set(satir.id, structuredClone(satir)); },
};
```

```js
// gonderi.mjs — kimligi olan alan nesnesi
export class Gonderi {
  constructor({ id, agirlik, indirim, aktarma }) {
    Object.assign(this, { id, agirlik, indirim, aktarma });
  }
  net() { return Math.round(3900 * this.agirlik * (1 - this.indirim)); }
}
```

Kopya vermek doğru karardır ama bir yan sonucu vardır: iki arama iki bağımsız nesne üretir ve
ikisi de aynı kaydı temsil ettiğini bilmez.

#### Çözüm

Harita, üretim işini kendi içine alır. Nesnenin nasıl üretileceğini bilmez; yalnız daha önce
üretilip üretilmediğini bilir.

```js
// kimlik-haritasi.mjs — istek boyunca kimlik basina tek nesne
export function kimlikHaritasi() {
  const harita = new Map();
  return {
    al(tur, id, uret) {
      const anahtar = `${tur}:${id}`;
      if (harita.has(anahtar)) return harita.get(anahtar);
      const nesne = uret();
      harita.set(anahtar, nesne);
      return nesne;
    },
    tutulan: () => harita.size,
  };
}
```

Anahtarın tür adını da içermesi zorunludur: iki ayrı türün aynı kimliği taşıması sıradan bir
durumdur. Erişim noktası tek bir yerde kalır.

```js
// kaynak.mjs — ayni esleyici; harita verilirse ayni kimlik ayni nesneyi dondurur
import { depo } from "./bellek-depo.mjs";
import { Gonderi } from "./gonderi.mjs";

export const kaynak = (harita) => ({
  bul(id) {
    const uret = () => {
      const satir = depo.oku("gonderi", id);
      return satir === null ? null : new Gonderi(satir);
    };
    return harita === null ? uret() : harita.al("gonderi", id, uret);
  },
  kaydet: (g) => depo.yaz("gonderi", { id: g.id, agirlik: g.agirlik, indirim: g.indirim, aktarma: g.aktarma }),
});
```

#### Tekilliğin Sayılması

Ölçüm iki koşum yapar. Beş arama üç ayrı kimliğe gider; ardından aynı kimlik iki kez aranır,
iki kod yolu ayrı alanları değiştirir ve ikisi de kaydedilir.

```js
// tekillik-say.mjs — haritasiz ve haritali kosumda ayri ornek, kayip alan ve tutulan nesne
import { depo } from "./bellek-depo.mjs";
import { kaynak } from "./kaynak.mjs";
import { kimlikHaritasi } from "./kimlik-haritasi.mjs";

const KIMLIKLER = ["G1", "G2", "G3"];
const ARAMALAR = ["G1", "G2", "G3", "G2", "G1"];

function kosu(ad, harita) {
  for (const id of KIMLIKLER) depo.yaz("gonderi", { id, agirlik: 3, indirim: 0, aktarma: ["34"] });
  const k = kaynak(harita);
  const ornekler = new Set(ARAMALAR.map((id) => k.bul(id)));

  const ucret = k.bul("G2"), yon = k.bul("G2");
  ucret.indirim = 0.15;
  yon.aktarma.push("06");
  k.kaydet(ucret);
  k.kaydet(yon);

  const son = depo.oku("gonderi", "G2");
  const beklenen = { indirim: 0.15, aktarma: 2 };
  const kayip = (son.indirim === beklenen.indirim ? 0 : 1) + (son.aktarma.length === beklenen.aktarma ? 0 : 1);
  console.log(`${ad}`);
  console.log(`  ${ARAMALAR.length} arama -> ayri ornek = ${ornekler.size}   ayni kimlik ayni nesne mi = ${ucret === yon}`);
  console.log(`  yazma sonrasi indirim = ${son.indirim}, aktarma = ${son.aktarma.length}, kayip alan = ${kayip}`);
  console.log(`  haritada tutulan nesne = ${harita === null ? 0 : harita.tutulan()}`);
}

kosu("haritasiz", null);
kosu("kimlik haritasi ile", kimlikHaritasi());
```

```sh
node tekillik-say.mjs
```

```
haritasiz
  5 arama -> ayri ornek = 5   ayni kimlik ayni nesne mi = false
  yazma sonrasi indirim = 0, aktarma = 2, kayip alan = 1
  haritada tutulan nesne = 0
kimlik haritasi ile
  5 arama -> ayri ornek = 3   ayni kimlik ayni nesne mi = true
  yazma sonrasi indirim = 0.15, aktarma = 2, kayip alan = 0
  haritada tutulan nesne = 3
```

Beş arama haritasız düzende 5 ayrı nesne üretti, haritalı düzende 3 — kimlik sayısı kadar.
Aynı kimliği iki kez arayan iki kod yolu haritasız düzende iki farklı nesne aldı ve `===`
karşılaştırması yanlış sonuç verdi; harita ile aynı nesneyi aldılar.

Kayıp alan sayısı farkın en somut biçimidir: haritasız koşumda indirim değişikliği kayboldu,
kaydedilen satırda indirim 0 kaldı. Haritalı koşumda iki değişiklik de aynı nesnenin üzerinde
birikti ve ikisi birlikte yazıldı.

Karşılıkta bir sayı daha var: harita 3 nesneyi bellekte tutuyor. Haritasız düzende tutulan
nesne 0'dır, üretilen nesneler kullanıldıktan sonra bırakılır. Kalıbın bedeli tam olarak
budur ve ikinci bir sonuç doğurur.

#### Haritanın Yaşam Süresi

Harita bir nesneyi tuttuğu sürece o nesne deponun sonraki hâlini görmez.

```js
// bayat.mjs — harita yasarken depo degisirse okunan deger hangisi olur
import { depo } from "./bellek-depo.mjs";
import { kaynak } from "./kaynak.mjs";
import { kimlikHaritasi } from "./kimlik-haritasi.mjs";

for (const [ad, harita] of [["haritasiz", null], ["kimlik haritasi ile", kimlikHaritasi()]]) {
  depo.yaz("gonderi", { id: "G9", agirlik: 3, indirim: 0.1, aktarma: ["34"] });
  const k = kaynak(harita);
  const ilk = k.bul("G9").indirim;
  depo.yaz("gonderi", { id: "G9", agirlik: 3, indirim: 0.3, aktarma: ["34"] });
  const ikinci = k.bul("G9").indirim;
  console.log(`${ad.padEnd(20)} ilk okuma = ${ilk}, depo 0.3 oldu, ikinci okuma = ${ikinci}, bayat = ${ikinci !== 0.3}`);
}
```

```sh
node bayat.mjs
```

```
haritasiz            ilk okuma = 0.1, depo 0.3 oldu, ikinci okuma = 0.3, bayat = false
kimlik haritasi ile  ilk okuma = 0.1, depo 0.3 oldu, ikinci okuma = 0.1, bayat = true
```

İkinci okuma haritasız düzende yeni değeri, haritalı düzende ilk değeri verdi. Bu bir kusur
değil, kalıbın tanımının gereğidir: harita bir nesnenin tekilliğini garanti eder, tazeliğini
garanti etmez. Buradan kalıbın uygulanma sınırı çıkar. Harita bir işin — tek bir isteğin ya
da tek bir iş biriminin — sınırında yaşar ve o sınır bittiğinde bırakılır. Süreç boyunca
yaşayan bir harita bir önbelleğe dönüşür ve tazelik sorusu sorulmaya başlar; o soru kimlik
haritasının cevapladığı soru değildir.

#### Ne Zaman Uygulanmaz

Kalıbın kazancı iki koşuldan doğar: nesnenin bir kimliği olması ve değiştirilebilir olması.
İkisinden biri yoksa kazanç sıfırlanır.

Değer nesnelerinde kimlik yoktur; iki bölge katsayısı aynı değeri taşıyorsa birbirinin yerine
geçer ve eşitlik değere göre kurulur. Değiştirilemez bir değer nesnesinde iki ayrı örneğin
birbirini ezmesi de olanaksızdır, dolayısıyla ölçümdeki kayıp alan sayısı harita olmadan da
0'dır. Kazanç 0, bedel ise haritanın tuttuğu nesne sayısı kadar.

Tek okuma yapan senaryolarda da kazanç yoktur: ayrı örnek sayısı zaten arama sayısına eşittir
ve arama sayısı 1'dir. Son sınır ölçekle ilgilidir. Harita süreç yereldir; iki ayrı süreç aynı
kaydı yüklerse her birinin haritası kendi nesnesini tutar ve tekillik süreçler arasında
korunmaz. Süreçler arası aynı anda yazma sorunu kilitleme konusuna aittir ve Veri Erişim
Katmanı kursunda ölçülmüştür.

#### Özet

- Kimlik haritası bir kimlik için üretilen nesneyi saklar ve aynı kimlik yeniden arandığında
  yeni nesne üretmek yerine saklananı döndürür.
- Beş arama haritasız düzende 5 ayrı örnek, haritalı düzende kimlik sayısı kadar 3 örnek
  üretti; aynı kimliği arayan iki kod yolu haritasız düzende `===` karşılaştırmasını
  geçemedi.
- İki kod yolu aynı kaydın ayrı alanlarını değiştirdiğinde haritasız koşumda 1 alan kayboldu;
  haritalı koşumda iki değişiklik aynı nesnede birikti ve kayıp 0 oldu.
- Bedel bellekte tutulan nesne sayısıdır (3'e karşı 0) ve ikinci bedel bayat okumadır: depo
  değiştiğinde haritalı düzen ilk değeri vermeye devam etti.
- Harita tek bir işin sınırında yaşar; değer nesnelerinde ve tek okumalı senaryolarda kazanç
  sıfırlanır, süreçler arasında tekillik korunmaz.

#### Sonraki Adım

Buraya kadarki dört kalıp iş mantığı ile kalıcılık arasındaki sınırı düzenledi. Alan nesnesi
artık kuralı taşıyor, kalıcılığı bilmiyor ve bellekte tekil. Ama bu nesne kitaplığın dışına da
çıkacak: bir arayüz onu listeleyecek, bir taşıyıcı sistemi ona rota soracak, bir fatura dökümü
ondan üretilecek. Alan nesnesinin dışarıya olduğu gibi verilmesi iç yapıyı dış sözleşme hâline
getirir ve iç yapı her değiştiğinde dış sözleşme de değişir. Sonraki ders katmanlar arası veri
taşımayı veri aktarım nesneleri ve eşleyicilerle kurar, iç modelin biçimi değiştiğinde bozulan
istemci çıktısı sayısını, istemci kodundaki iç alan adı sayısını ve eşlemenin alan başına satır
bedelini sayar.

### Veri Aktarım Nesneleri ve Eşleyiciler

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/veri-aktarim-nesneleri-ve-esleyiciler

Katmanlar arası veri taşımanın kalıp düzeyinde ele alınması: iç modelin biçimi değiştiğinde doğrudan okuyan istemcilerin bozulmasıyla eşleyici arkasındaki istemcilerin bozulmamasının sayılması, istemci kodundaki iç alan adı sayısı ve eşlemenin alan başına satır bedeli.

Buraya kadarki dört kalıp iş mantığı ile kalıcılık arasındaki sınırı düzenledi. Alan nesnesi
kuralı taşıyor, kalıcılığı bilmiyor ve bellekte tekil. Bu nesne kitaplığın dışına da çıkacak:
bir arayüz onu listeleyecek, bir fatura dökümü ondan üretilecek, bir kütük satırı onu
kaydedecek. Nesnenin dışarıya olduğu gibi verilmesi iç yapıyı dış sözleşme hâline getirir.

**Veri aktarım nesnesi** (data transfer object) bu sözleşmeyi ayrı bir düz kayıt olarak
tanımlar; **eşleyici** iç modelden o kaydı üretir. Kalıp Veri Erişim Katmanı kursunda
kurulmuş ve orada dış sözleşmeye sızan alan sayısıyla ölçülmüştü. Burada ölçü değişim
tarafındadır: iç modelin biçimi değiştiğinde kaç istemci çıktısı bozuluyor, istemci kodu kaç
iç alan adı taşıyor, eşleme alan başına kaç satıra mal oluyor.

#### Problem: İç Biçim Dışarıdan Okunursa

İç model aynı bilgiyi iki farklı biçimde tutabilir. İkinci sürümde ücretle ilgili alanlar iç
içe bir nesneye taşınmış durumda; taşınan bilgi aynı, adres farklı.

```js
// alan.mjs — ayni bilginin iki ic modeli: ikincisinde ucret alanlari ic ice tasindi
export const surumBir = () => ({
  id: "G7", agirlik: 3, postaKodu: "06800", notlar: "sozlesmeli musteri",
  ham: 8640, indirimler: [{ ad: "sozlesme", oran: 0.15 }, { ad: "hacim", oran: 0.1 }],
});

export const surumIki = () => ({
  id: "G7", agirlik: 3, postaKodu: "06800", notlar: "sozlesmeli musteri",
  ucret: { taban: 8640, indirimler: [{ ad: "sozlesme", oran: 0.15 }, { ad: "hacim", oran: 0.1 }] },
});
```

Üç istemci bu nesneyi doğrudan okur ve kendi biçimini üretir.

```js
// dtosuz/istemciler.mjs — uc istemci ic modelin alanlarini dogrudan okuyup bicimler
const TAVAN = 0.4;
const oran = (g) => Math.min(g.indirimler.reduce((t, i) => t + i.oran, 0), TAVAN);
const net = (g) => Math.round(g.ham * (1 - oran(g)));

export const liste = (g) => `${g.id}  ${g.agirlik} kg  ${net(g)}`;
export const fatura = (g) =>
  `${g.id}: taban ${g.ham}, ${g.indirimler.map((i) => `${i.ad} ${-Math.round(g.ham * i.oran)}`).join(", ")} = ${net(g)}`;
export const kutuk = (g) => `${g.id} net=${net(g)} indirim=${g.indirimler.length}`;
```

Üç istemcinin üçü de `ham` ve `indirimler` adlarını biliyor. İç modelin bu iki adı taşıdığı
varsayımı böylece üç ayrı yere yayılmış oldu.

#### Çözüm: Sözleşmeyi Ayrı Kayıt Olarak Tanımlamak

Eşleyici, iç modelden dış sözleşmeyi üretir. İki iç sürüm için iki üretim işlevi vardır ama
ürettikleri kayıtlar aynıdır.

```js
// dto/esleyici.mjs — ic modelin her surumu icin ayni uc veri aktarim nesnesini uretir
const TAVAN = 0.4;

const kur = (id, agirlik, taban, indirimler) => {
  const oran = Math.min(indirimler.reduce((t, i) => t + i.oran, 0), TAVAN);
  const net = Math.round(taban * (1 - oran));
  return {
    liste: { kimlik: id, agirlik, net },
    fatura: { kimlik: id, taban, kalemler: indirimler.map((i) => ({ ad: i.ad, tutar: -Math.round(taban * i.oran) })), toplam: net },
    kutuk: { kimlik: id, net, indirimSayisi: indirimler.length },
  };
};

export const esleyiciBir = (g) => kur(g.id, g.agirlik, g.ham, g.indirimler);
export const esleyiciIki = (g) => kur(g.id, g.agirlik, g.ucret.taban, g.ucret.indirimler);
```

```js
// dto/istemciler.mjs — uc istemci yalniz veri aktarim nesnesinin alanlarini okur
export const liste = (d) => `${d.liste.kimlik}  ${d.liste.agirlik} kg  ${d.liste.net}`;
export const fatura = (d) =>
  `${d.fatura.kimlik}: taban ${d.fatura.taban}, ${d.fatura.kalemler.map((k) => `${k.ad} ${k.tutar}`).join(", ")} = ${d.fatura.toplam}`;
export const kutuk = (d) => `${d.kutuk.kimlik} net=${d.kutuk.net} indirim=${d.kutuk.indirimSayisi}`;
```

Sözleşme adlarının iç adlardan ayrıldığına dikkat etmek gerekir: içeride `id`, dışarıda
`kimlik`. Ayrım kasıtlıdır; iki adın aynı olması bir kolaylık gibi görünüp iki tarafı yeniden
birbirine bağlar.

#### Değişim Yayılımının Sayılması

Ölçüm iç modeli birinci sürümden ikinci sürüme geçirir ve iki düzende ne olduğunu sayar. Bir
çıktı `undefined` ya da `NaN` içeriyorsa, ya da hata atıyorsa bozulmuş sayılır.

```js
// yayilim-say.mjs — ic model degistiginde bozulan istemci sayisi ve istemcideki ic alan adi
import { readFileSync } from "node:fs";
import { surumBir, surumIki } from "./alan.mjs";
import * as dogrudan from "./dtosuz/istemciler.mjs";
import { esleyiciBir, esleyiciIki } from "./dto/esleyici.mjs";
import * as dto from "./dto/istemciler.mjs";

const ADLAR = ["liste", "fatura", "kutuk"];
const dene = (f, girdi) => { try { return f(girdi); } catch { return "HATA"; } };
const saglam = (s) => /undefined|NaN|HATA/.test(s) === false;

function kosu(ad, birinci, ikinci) {
  const ilk = ADLAR.map((a) => dene(birinci[0][a], birinci[1]));
  const son = ADLAR.map((a) => dene(ikinci[0][a], ikinci[1]));
  const bozulan = son.filter((s) => saglam(s) === false).length;
  const degisen = son.filter((s, i) => s !== ilk[i]).length;
  console.log(`${ad}: bozulan istemci cikti = ${bozulan} / ${ADLAR.length}, degisen cikti = ${degisen} / ${ADLAR.length}`);
  return son;
}

console.log("ic model surum 1 -> surum 2");
kosu("dtosuz    ", [dogrudan, surumBir()], [dogrudan, surumIki()]);
const sonuc = kosu("dto ile   ", [dto, esleyiciBir(surumBir())], [dto, esleyiciIki(surumIki())]);
for (const s of sonuc) console.log(`  ${s}`);

const IC_ALAN = /\bg\.(ham|indirimler|notlar)\b|\bucret\.(taban|indirimler)\b/g;
for (const dosya of ["dtosuz/istemciler.mjs", "dto/istemciler.mjs", "dto/esleyici.mjs"]) {
  const m = readFileSync(dosya, "utf8");
  console.log(`${dosya.padEnd(24)} ic alan adi = ${[...m.matchAll(IC_ALAN)].length}`);
}

const dtoAlan = Object.values(esleyiciBir(surumBir())).reduce((t, d) => t + Object.keys(d).length, 0);
const satir = readFileSync("dto/esleyici.mjs", "utf8").split("\n").filter((s) => s.trim() !== "").length;
console.log(`dto alan sayisi = ${dtoAlan}, esleyici satir = ${satir}, alan basina satir = ${(satir / dtoAlan).toFixed(2)}`);
```

```sh
node yayilim-say.mjs
```

```
ic model surum 1 -> surum 2
dtosuz    : bozulan istemci cikti = 3 / 3, degisen cikti = 3 / 3
dto ile   : bozulan istemci cikti = 0 / 3, degisen cikti = 0 / 3
  G7  3 kg  6480
  G7: taban 8640, sozlesme -1296, hacim -864 = 6480
  G7 net=6480 indirim=2
dtosuz/istemciler.mjs    ic alan adi = 6
dto/istemciler.mjs       ic alan adi = 0
dto/esleyici.mjs         ic alan adi = 4
dto alan sayisi = 10, esleyici satir = 13, alan basina satir = 1.30
```

İç modelin biçimi değiştiğinde doğrudan okuyan üç istemcinin üçü de bozuldu. Eşleyici
arkasındaki üç istemcinin hiçbiri bozulmadı ve üç çıktının üçü birinci sürümdekiyle birebir
aynı kaldı — çıktı satırları bunu gösteriyor.

Sayının kaynağı sonraki üç satırda duruyor: istemci dosyası doğrudan okuyan düzende 6 iç alan
adı taşıyor, eşleyicili düzende 0. İç yapı hakkındaki bilgi 4 adla tek dosyada, eşleyicinin
içinde toplanmış durumda. Değişim yayılımı istemci sayısıyla büyüyen bir çarpımdan tek dosyalık
sabite indi.

#### Bedelin Sayılması

Son satır bedeli veriyor. Üç sözleşme toplam 10 alan taşıyor ve eşleyici bunu 13 satırda
üretiyor: alan başına 1,3 satır. Bu satırların işlevsel bir karşılığı yoktur; iç adı dış ada
bağlamaktan başka iş yapmazlar. Yanına iki ek bedel gelir: 2 yeni dosya ve sınırdan her
geçişte 1 ek çağrı.

İkinci bedel bakım tarafındadır. Sözleşmeye bir alan eklenmesi iki yerde iş çıkarır: eşleyici
o alanı üretmeli, istemci onu okumalı. Alan üretilmezse istemci `undefined` görür ve ölçümdeki
bozulma sayacı bunu yakalar. Bu nedenle eşleyicinin ürettiği kayıt bir sınamayla
kilitlenmelidir; sözleşme sınaması olmadan eşleyici sessizce eksik kalabilir.

#### Ne Zaman Uygulanmaz

Kalıbın kazancı iki sayının çarpımıdır: iç yapıya bağlı istemci sayısı ile iç yapının değişme
sıklığı. İkisinden biri düşükse kazanç bedelin altına iner.

Tek istemcili bir sınırda iç modelin değişmesi zaten o tek istemciyi ilgilendirir; eşleyici
onu 0 dosyadan korur ve karşılığında 13 satır ile 2 dosya ödetir. Aynı süreç içinde kalan ve
aynı takımın baktığı çağrılarda da durum budur: alan nesnesini doğrudan geçirmek 0 satır
tutar.

Ters yönde bir sınır daha var. Sözleşme iç modelin alan adlarını birebir kopyalıyorsa veri
aktarım nesnesi bir yalıtım kurmaz, yalnız bir kopyalama katmanı ekler. Ölçüde bu durum şöyle
görünür: iç model değiştiğinde eşleyici de değişir ve bozulma sayısı yine 3'e çıkar, çünkü
sözleşme iç yapının aynası olmuştur. Kalıbın işe yaraması için sözleşmenin iç modelden
**bağımsız** bir kararla tanımlanmış olması gerekir. Bu ayrımın adı Tasarım İlkeleri kursunda
konulmuştu: politika ile ayrıntının ayrılması.

#### Özet

- Veri aktarım nesnesi dış sözleşmeyi ayrı bir düz kayıt olarak tanımlar; eşleyici iç
  modelden o kaydı üretir ve iç yapı bilgisini tek dosyada toplar.
- İç modelin biçimi değiştiğinde doğrudan okuyan 3 istemcinin 3'ü bozuldu; eşleyici
  arkasındaki 3 istemcinin 0'ı bozuldu ve üç çıktı birebir aynı kaldı.
- İstemci dosyası doğrudan okuyan düzende 6 iç alan adı taşıyordu, eşleyicili düzende 0;
  bilgi 4 adla eşleyicinin içine toplandı.
- Bedel sayıldı: 10 sözleşme alanı için 13 eşleyici satırı (alan başına 1,3 satır), 2 yeni
  dosya ve sınırdan her geçişte 1 ek çağrı.
- Kazanç istemci sayısı ile iç modelin değişme sıklığının çarpımıdır; tek istemcide ya da
  sözleşme iç adları birebir kopyaladığında kalıp bir kopyalama katmanına dönüşür.

#### Sonraki Adım

Bu dersin istemcileri kitaplığa hangi kapıdan girdiklerini hiç sormadı; eşleyicinin ürettiği
kaydı hazır aldılar. Kaydı kimin ürettiği, senaryonun hangi sırayla yürüdüğü ve iş biriminin
nerede açılıp kapandığı hâlâ belirsiz. Dört istemci aynı senaryoyu kendi içinde tekrar ederse
adımlar dört kez yazılır. Sonraki ders uygulama sınırını servis katmanıyla tanımlar, istemci
dosyasındaki altyapı adı sayısını ve birden fazla dosyada tekrarlanan senaryo adımı sayısını
ölçer, yalnız çağrıyı ileten yöntem sayısıyla servis katmanının kazançsız kaldığı durumu
belirler.

### Servis Katmanı

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/servis-katmani

Uygulama sınırının tek bir yerde tanımlanması: senaryoyu kendi içinde yürüten iki istemci ile aynı senaryoyu bir kez yazan servis katmanının karşılaştırılması, istemci dosyasındaki altyapı adı ve tekrarlanan senaryo adımı sayısının sayılması, yalnız çağrıyı ileten yöntem sayısıyla ince servisin ölçülmesi.

Önceki dersin istemcileri eşleyicinin ürettiği kaydı hazır aldı; kaydı kimin ürettiği, hangi
adımların hangi sırayla yürüdüğü ve iş biriminin nerede açılıp kapandığı sorulmadı. Bu sorular
bir yerde yanıtlanmak zorundadır. Yanıt her istemcinin içinde tekrarlanırsa senaryo istemci
sayısı kadar yazılır.

**Servis katmanı** (service layer) bu yanıtı tek bir yere koyar: uygulamanın dışarıya açtığı
işlem kümesini tanımlar, her işlemin adım sırasını yürütür, iş biriminin sınırını belirler ve
dışarıya sözleşme kaydı döndürür. Katman sorumlulukları ve bağımlılık yönü Veri Erişim Katmanı
kursunda ölçülmüştü; burada ölçülen şey sınırın kendisidir: istemcinin bilmek zorunda olduğu
altyapı adı sayısı ve birden fazla dosyada tekrarlanan senaryo adımı sayısı.

Ölçüm için önceki derslerin parçaları derli bir dosyada toplanır.

```js
// altyapi.mjs — onceki derslerin parcalari derli bicimde: depo, is birimi, alan kurali, esleyici
const TABLO = new Map();
export const depo = {
  oku: (id) => structuredClone(TABLO.get(id) ?? null),
  yaz: (satir) => { TABLO.set(satir.id, structuredClone(satir)); },
};

export const gonderiDeposu = { bul: (id) => depo.oku(id), yeni: (satir) => depo.yaz(satir) };

export function isBirimi() {
  const kirli = new Map();
  return {
    kirlet: (satir) => kirli.set(satir.id, satir),
    tamamla() { for (const s of kirli.values()) depo.yaz(s); return kirli.size; },
  };
}

export const kural = {
  indirimEkle: (t, indirim) => ({ ...t, indirimler: [...t.indirimler, indirim] }),
  aktarmaEkle: (t, nokta) => ({ ...t, aktarma: [...t.aktarma, nokta] }),
  net: (t) => Math.round(t.taban * (1 - Math.min(t.indirimler.reduce((s, i) => s + i.oran, 0), 0.4))),
};

export const esleyici = { liste: (t) => ({ kimlik: t.id, net: kural.net(t), aktarma: t.aktarma.length }) };
```

#### Problem: Senaryo Her İstemcide Yeniden Yazılırsa

Kitaplığın iki istemcisi var: gelen bir isteği karşılayan işleyici ve gece çalışan toplu
betik. İkisi de aynı işi ister — bir gönderiye indirim ve aktarma noktası eklemek — ve ikisi
de adımları kendi içinde yürütür.

```js
// dogrudan/istemci-a.mjs — istek isleyicisi senaryonun adimlarini kendi yurutur
import { gonderiDeposu, isBirimi, kural, esleyici } from "../altyapi.mjs";

export function indirimIste(id, indirim, nokta) {
  let t = gonderiDeposu.bul(id);
  if (t === null) throw new RangeError(`gonderi yok: ${id}`);
  t = kural.indirimEkle(t, indirim);
  t = kural.aktarmaEkle(t, nokta);
  const birim = isBirimi();
  birim.kirlet(t);
  birim.tamamla();
  return esleyici.liste(t);
}
```

```js
// dogrudan/istemci-b.mjs — toplu betik ayni adimlari yeniden yazar
import { gonderiDeposu, isBirimi, kural, esleyici } from "../altyapi.mjs";

export function topluIndirim(kimlikler, indirim, nokta) {
  return kimlikler.map((id) => {
    let t = gonderiDeposu.bul(id);
    if (t === null) throw new RangeError(`gonderi yok: ${id}`);
    t = kural.indirimEkle(t, indirim);
    t = kural.aktarmaEkle(t, nokta);
    const birim = isBirimi();
    birim.kirlet(t);
    birim.tamamla();
    return esleyici.liste(t);
  });
}
```

İkinci dosya birincisinin gövdesini bir döngünün içine almış hâlidir. Tekrarlanan şey kod
satırı değil, **karar**: adımların sırası, doğrulamanın nerede yapıldığı, işlem sınırının
nereye çizildiği.

#### Çözüm: Sınırı Bir Kez Yazmak

```js
// servis/gonderi-servisi.mjs — senaryo bir kez burada: sira, islem siniri ve donen sozlesme
import { gonderiDeposu, isBirimi, kural, esleyici } from "../altyapi.mjs";

export const gonderiServisi = {
  indirimVeAktarmaEkle(id, indirim, nokta) {
    let t = gonderiDeposu.bul(id);
    if (t === null) throw new RangeError(`gonderi yok: ${id}`);
    t = kural.indirimEkle(t, indirim);
    t = kural.aktarmaEkle(t, nokta);
    const birim = isBirimi();
    birim.kirlet(t);
    birim.tamamla();
    return esleyici.liste(t);
  },
};
```

```js
// servis/istemciler.mjs — iki istemci ayni uygulama sinirini cagirir
import { gonderiServisi } from "./gonderi-servisi.mjs";

export const indirimIste = (id, indirim, nokta) => gonderiServisi.indirimVeAktarmaEkle(id, indirim, nokta);
export const topluIndirim = (kimlikler, indirim, nokta) =>
  kimlikler.map((id) => gonderiServisi.indirimVeAktarmaEkle(id, indirim, nokta));
```

Toplu betiğin tek işi kaldı: döngü. Tekil ile toplu arasındaki gerçek fark buydu; kalan her
şey ortaktı.

#### Sınırın Sayılması

Ölçüm iki tür sayı çıkarır. Birincisi statik: her dosyanın kaç altyapı adı bildiği ve kaç
senaryo adımı taşıdığı. İkincisi koşumdan: iki düzenin aynı sözleşmeyi üretip üretmediği.

```js
// sinir-say.mjs — istemci basina ithal edilen altyapi adi, tekrar eden senaryo adimi ve cikti esitligi
import { readFileSync } from "node:fs";
import { gonderiDeposu } from "./altyapi.mjs";
import * as dogrudanA from "./dogrudan/istemci-a.mjs";
import * as dogrudanB from "./dogrudan/istemci-b.mjs";
import * as servis from "./servis/istemciler.mjs";

const ADIM = {
  arama: /gonderiDeposu\.bul\(/, dogrulama: /throw new RangeError/, indirim: /kural\.indirimEkle\(/,
  aktarma: /kural\.aktarmaEkle\(/, birim: /isBirimi\(\)/, tamamla: /\.tamamla\(\)/, sozlesme: /esleyici\.liste\(/,
};
const ALTYAPI = /\b(gonderiDeposu|isBirimi|kural|esleyici)\b/g;

function tara(dosyalar) {
  const adimlar = dosyalar.map((d) => {
    const m = readFileSync(d, "utf8");
    const bulunan = Object.keys(ADIM).filter((k) => ADIM[k].test(m));
    const ithal = new Set([...m.matchAll(ALTYAPI)].map(([a]) => a));
    console.log(`  ${d.padEnd(26)} altyapi adi = ${ithal.size}, senaryo adimi = ${bulunan.length}`);
    return bulunan;
  });
  const tumu = adimlar.flat();
  const tekrar = new Set(tumu.filter((a, i) => tumu.indexOf(a) !== i));
  console.log(`  toplam adim yazimi = ${tumu.length}, birden fazla dosyada tekrarlanan adim = ${tekrar.size}`);
}

for (const id of ["G1", "G2"]) gonderiDeposu.yeni({ id, taban: 8640, indirimler: [], aktarma: ["34"] });
const INDIRIM = { ad: "sozlesme", oran: 0.15 };

console.log("servis katmani yok");
tara(["dogrudan/istemci-a.mjs", "dogrudan/istemci-b.mjs"]);
console.log("servis katmani var");
tara(["servis/istemciler.mjs", "servis/gonderi-servisi.mjs"]);

const a = dogrudanA.indirimIste("G1", INDIRIM, "06");
const b = servis.indirimIste("G2", INDIRIM, "06");
const c = dogrudanB.topluIndirim(["G1"], INDIRIM, "35")[0];
const d = servis.topluIndirim(["G2"], INDIRIM, "35")[0];
const es = (x, y) => x.net === y.net && x.aktarma === y.aktarma;
console.log(`tek istek ciktisi esit mi = ${es(a, b)}, toplu cikti esit mi = ${es(c, d)}`);
console.log(`ornek sozlesme = ${JSON.stringify(b)}`);
```

```sh
node sinir-say.mjs
```

```
servis katmani yok
  dogrudan/istemci-a.mjs     altyapi adi = 4, senaryo adimi = 7
  dogrudan/istemci-b.mjs     altyapi adi = 4, senaryo adimi = 7
  toplam adim yazimi = 14, birden fazla dosyada tekrarlanan adim = 7
servis katmani var
  servis/istemciler.mjs      altyapi adi = 0, senaryo adimi = 0
  servis/gonderi-servisi.mjs altyapi adi = 4, senaryo adimi = 7
  toplam adim yazimi = 7, birden fazla dosyada tekrarlanan adim = 0
tek istek ciktisi esit mi = true, toplu cikti esit mi = true
ornek sozlesme = {"kimlik":"G2","net":7344,"aktarma":2}
```

Üç sayı değişti. İstemci dosyasının bildiği altyapı adı 4'ten 0'a indi: sunum tarafı artık
deponun, iş biriminin, alan kuralının ve eşleyicinin adını taşımıyor, tek bir servis adı
taşıyor. Toplam adım yazımı 14'ten 7'ye indi. Birden fazla dosyada tekrarlanan adım sayısı
7'den 0'a indi; yedi kararın hepsi tek dosyada toplandı.

Son iki satır davranışın korunduğunu gösteriyor: tek istek ve toplu koşum aynı sözleşmeyi
üretti. Sayıların büyüme biçimi de önemlidir. İstemci sayısı `n` iken servis katmanı olmayan
düzende adım yazımı `7n`, servis katmanlı düzende sabit 7'dir; kazanç istemci sayısıyla
doğrusal büyür.

#### Bedel: İnce Servis

Servis katmanı 1 dosya ekler ve istemciden alan kuralına giden yolu 1 basamak uzatır. Bu bedel
karşılığını yalnız servis yönteminin **kendine ait bir kararı** olduğunda alır. Karar yoksa
yöntem çağrıyı iletmekten başka iş yapmaz.

```js
// servis/ince-servis.mjs — her yontemi tek bir cagriyi ileten servis
import { gonderiDeposu, kural } from "../altyapi.mjs";

export const inceServis = {
  bul: (id) => gonderiDeposu.bul(id),
  net: (t) => kural.net(t),
  indirimEkle: (t, i) => kural.indirimEkle(t, i),
};
```

```js
// ileten-say.mjs — servis yontemlerinin kaci yalniz cagriyi iletiyor
import { readFileSync } from "node:fs";

const YONTEM = /^\s{2}(\w+)[:(]/;
const ILETEN = /^\s{2}\w+:\s*\([^)]*\)\s*=>\s*[\w.]+\([^;]*\),$/;

for (const dosya of ["servis/gonderi-servisi.mjs", "servis/ince-servis.mjs"]) {
  const satirlar = readFileSync(dosya, "utf8").split("\n");
  const yontem = satirlar.filter((s) => YONTEM.test(s)).length;
  const ileten = satirlar.filter((s) => ILETEN.test(s)).length;
  console.log(`${dosya.padEnd(26)} yontem = ${yontem}, yalniz ileten = ${ileten}`);
}
```

```sh
node ileten-say.mjs
```

```
servis/gonderi-servisi.mjs yontem = 1, yalniz ileten = 0
servis/ince-servis.mjs     yontem = 3, yalniz ileten = 3
```

İnce serviste üç yöntemin üçü yalnız çağrıyı iletiyor. Bu düzen 1 dosya ve 3 ad ekler,
karşılığında 0 karar toplar; istemcinin bilmek zorunda olduğu ad sayısını da düşürmez, yalnız
adları değiştirir.

#### Ne Zaman Uygulanmaz

Ölçüdeki kazanç iki koşula bağlıdır: istemci sayısının birden fazla olması ve senaryonun birden
çok adım içermesi.

Tek istemcili bir kitaplıkta tekrarlanan adım sayısı zaten 0'dır; servis katmanı 7'yi 7'de
bırakır ve 1 dosya ile 1 dolaylılık basamağı ödetir. Tek adımlı işlemlerde de kazanç yoktur:
bir gönderiyi kimliğiyle okuyup döndüren bir işlem için servis yöntemi ileten-yöntem sayacına
takılır.

Üçüncü sınır kalınlık kararıdır. Servis yöntemi iş kuralını kendi içine yazmaya başlarsa
kuralın uygulama noktası sayısı yeniden çoğalır — bu, İşlem Betiği ve Alan Modeli dersinde
sayılan şeydir. Servis katmanının işi sıra, sınır ve sözleşmedir; karar alan modelinde kalır.

#### Özet

- Servis katmanı uygulamanın dışarıya açtığı işlem kümesini tanımlar; adım sırasını, iş
  biriminin sınırını ve dönen sözleşmeyi tek dosyada toplar.
- İki istemcili ölçümde istemci dosyasının bildiği altyapı adı 4'ten 0'a, toplam adım yazımı
  14'ten 7'ye, tekrarlanan adım sayısı 7'den 0'a indi.
- Tek istek ve toplu koşum aynı sözleşmeyi üretti; kalıp davranışı değiştirmedi, kararların
  hangi dosyada durduğunu değiştirdi.
- Kazanç istemci sayısıyla doğrusal büyür: adım yazımı servis olmadan `7n`, servisle sabit 7.
- Kendine ait kararı olmayan servis ince kalır; üç yöntemin üçü yalnız çağrıyı iletti, 1 dosya
  ve 3 ad karşılığında 0 karar toplandı.

#### Sonraki Adım

Buraya kadarki altı kalıp gönderi türünün tek olduğunu varsaydı: her gönderinin ağırlığı,
tabanı, indirimleri ve rotası var. Kitaplığa ikinci bir hizmet türü girdiğinde — soğuk zincir
gönderisi, değerli eşya gönderisi, belge gönderisi — her türün kendi nitelikleri ve kendi
ücret kuralı olur. Her tür için yeni bir sınıf yazmak, kaç dosyanın düzenlendiğini artıran bir
karardır; türler bir veri kaynağından geliyorsa sınıf yazmak zaten olanaksızdır. Sonraki ders
türü çalışma zamanında bir nesne olarak modelleyen tip nesnesini ve niteliklerin türe göre
değişmesini yöneten nitelik varyantlarını kurar, yeni tür eklendiğinde düzenlenen dosya ve
eklenen satır sayısını sayar.

### Tip Nesnesi ve Nitelik Varyantları

Source: https://academia.sh/tr/kurslar/tasarim-kaliplari/tip-nesnesi-ve-nitelik-varyantlari

Türün çalışma zamanında modellenmesi: her hizmet türü için sınıf yazan düzen ile türü veri olarak taşıyan tip nesnesinin karşılaştırılması, tür adı taşıyan kod dosyası sayısının ve yeni tür eklemenin maliyetinin sayılması, nitelik torbasının çalışma zamanında doğrulanması ve kaybedilen yazım zamanı denetiminin bedeli.

Buraya kadarki altı kalıp gönderi türünün tek olduğunu varsaydı. Kitaplığa ikinci bir hizmet
türü girdiğinde — soğuk zincir gönderisi, değerli eşya gönderisi, belge gönderisi — her türün
kendi nitelikleri ve kendi ücret çarpanı olur. Türü koda yazmak, tür sayısı arttıkça
düzenlenen dosya sayısını artıran bir karardır; türler bir veri kaynağından geliyorsa kod
yazmak zaten olanaksızdır.

**Tip nesnesi** (type object) türü bir sınıf olarak değil, bir nesne olarak modeller: türün
adı, gerektirdiği nitelikler ve ücret davranışı veri hâline gelir. **Nitelik varyantları** ise
bunun ikinci yarısıdır: her türün farklı bir nitelik kümesi taşıması, gönderi kaydının sabit
alanlar yerine bir **nitelik torbası** (property bag) tutmasıyla çözülür ve torbanın geçerliliği
tip nesnesine sorulur.

#### Sınıf Başına Tür

İlk düzende her hizmet türü bir sınıftır; nitelik listesi ve çarpan sınıfın içinde durur.

```js
// siniflar/turler.mjs — her hizmet turu bir sinif; nitelik listesi ve carpan kodda
export class Standart {
  static nitelikler = [];
  ucret(g) { return Math.round(g.taban); }
}

export class SogukZincir {
  static nitelikler = ["sicaklik"];
  ucret(g) { return Math.round(g.taban * 1.6); }
}

export class Degerli {
  static nitelikler = ["sigortaDegeri"];
  ucret(g) { return Math.round(g.taban * 1.25 + g.nitelikler.sigortaDegeri * 0.01); }
}
```

```js
// siniflar/kayit.mjs — tur anahtarini sinifa baglayan kayit
import { Standart, SogukZincir, Degerli } from "./turler.mjs";

const KAYIT = { "standart": Standart, "soguk-zincir": SogukZincir, "degerli": Degerli };

export function ucret(g) {
  const Tur = KAYIT[g.tur];
  if (Tur === undefined) throw new RangeError(`bilinmeyen hizmet turu: ${g.tur}`);
  return new Tur().ucret(g);
}
```

Düzenin kazancı her türün davranışını kendi metninde tutmasıdır. Kaybı, tür kümesinin kodun
bir parçası olmasıdır: yeni tür iki dosyayı da düzenlemek anlamına gelir.

#### Tip Nesnesi

İkinci düzende tür bir nesnedir. Kod hiçbir tür adını bilmez; yalnız bir türün nasıl bir şey
olduğunu bilir.

```js
// tip/tip-nesnesi.mjs — tur bir nesne olarak modellenir; kodda hicbir tur adi gecmez
export class TipNesnesi {
  constructor({ ad, nitelikler, carpan, ek }) {
    Object.assign(this, { ad, nitelikler, carpan, ek: ek ?? null });
  }
  dogrula(g) {
    const torba = g.nitelikler ?? {};
    return [
      ...this.nitelikler.filter((n) => n in torba === false).map((n) => `eksik nitelik: ${n}`),
      ...Object.keys(torba).filter((n) => this.nitelikler.includes(n) === false).map((n) => `taninmayan nitelik: ${n}`),
    ];
  }
  ucret(g) {
    const ek = this.ek === null ? 0 : (g.nitelikler?.[this.ek.alan] ?? 0) * this.ek.oran;
    return Math.round(g.taban * this.carpan + ek);
  }
}

export const tipKatalogu = (json) => new Map(JSON.parse(json).map((t) => [t.ad, new TipNesnesi(t)]));
```

Türlerin kendisi veridir. Aşağıdaki dosya bir kod dosyası değil, veri taşıyan bir dosyadır;
aynı metin bir yapılandırma kaydından ya da bir tablodan da gelebilirdi.

```js
// tip/tur-verisi.mjs — hizmet turleri veri olarak: her satir bir tur
export const TUR_JSON = `[
  { "ad": "standart", "nitelikler": [], "carpan": 1 },
  { "ad": "soguk-zincir", "nitelikler": ["sicaklik"], "carpan": 1.6 },
  { "ad": "degerli", "nitelikler": ["sigortaDegeri"], "carpan": 1.25, "ek": { "alan": "sigortaDegeri", "oran": 0.01 } }
]`;
```

`ek` alanı nitelik varyantlarının ücrete karışma biçimidir: değerli eşyada sigorta değerinin
yüzde biri ücrete eklenir, öteki türlerde böyle bir kural yoktur. Sınıf tabanlı düzende bu
kural bir yöntem gövdesiydi; burada iki alanlı bir kayıt.

#### Ölçüm

Üç şey sayılır: tür adının kaç kod dosyasında durduğu, yeni bir türün eklenmesinin neye mal
olduğu ve geçersiz niteliklerin kaçının yakalandığı.

```js
// tur-say.mjs — tur adinin kac kod dosyasinda durdugu ve yeni turun eklenme maliyeti
import { readFileSync } from "node:fs";
import { ucret as sinifUcreti } from "./siniflar/kayit.mjs";
import { tipKatalogu } from "./tip/tip-nesnesi.mjs";
import { TUR_JSON } from "./tip/tur-verisi.mjs";

const TUR_ADI = /Standart|SogukZincir|Degerli|"(standart|soguk-zincir|degerli)"/;
const KOD = { "sinif tabanli": ["siniflar/turler.mjs", "siniflar/kayit.mjs"], "tip nesnesi ": ["tip/tip-nesnesi.mjs"] };

for (const [ad, dosyalar] of Object.entries(KOD)) {
  const metinler = dosyalar.map((d) => readFileSync(d, "utf8"));
  const dokunan = metinler.filter((m) => TUR_ADI.test(m)).length;
  const liste = metinler.reduce((t, m) => t + [...m.matchAll(/static nitelikler|"nitelikler"/g)].length, 0);
  console.log(`${ad}: tur adi tasiyan kod dosyasi = ${dokunan} / ${dosyalar.length}, kodda nitelik listesi = ${liste}`);
}
console.log(`tur verisi  : ${JSON.parse(TUR_JSON).length} tur, tip/tur-verisi.mjs veri dosyasidir`);

const BELGE = `{ "ad": "belge", "nitelikler": ["sayfa"], "carpan": 0.7 }`;
const katalog = tipKatalogu(`[${TUR_JSON.slice(1, -1).trim()},\n  ${BELGE}\n]`);
const g = { tur: "belge", taban: 8640, nitelikler: { sayfa: 4 } };

let sinifSonuc;
try { sinifSonuc = String(sinifUcreti(g)); } catch (e) { sinifSonuc = `hata: ${e.message}`; }
console.log(`\nyeni tur "belge" eklendi (1 veri satiri, 0 kod satiri)`);
console.log(`  sinif tabanli -> ${sinifSonuc}`);
console.log(`  tip nesnesi   -> ${katalog.get("belge").ucret(g)}, katalogdaki tur sayisi = ${katalog.size}`);

const HATALILAR = [
  { tur: "soguk-zincir", taban: 8640, nitelikler: {} },
  { tur: "degerli", taban: 8640, nitelikler: { sigortaDegeri: 50000, sicaklik: -18 } },
  { tur: "standart", taban: 8640, nitelikler: { sayfa: 4 } },
];
let yakalanan = 0, sinifYakalanan = 0;
for (const h of HATALILAR) {
  const bulgular = katalog.get(h.tur).dogrula(h);
  if (bulgular.length > 0) yakalanan += 1;
  let sinif;
  try { sinif = String(sinifUcreti(h)); } catch (e) { sinif = `hata: ${e.name}`; sinifYakalanan += 1; }
  console.log(`  ${h.tur.padEnd(13)} tip nesnesi: ${bulgular.join("; ") || "bulgu yok"}  | sinif tabanli: ${sinif}`);
}
console.log(`dogrulama: tip nesnesi ${yakalanan}/${HATALILAR.length}, sinif tabanli ${sinifYakalanan}/${HATALILAR.length}`);
```

```sh
node tur-say.mjs
```

```
sinif tabanli: tur adi tasiyan kod dosyasi = 2 / 2, kodda nitelik listesi = 3
tip nesnesi : tur adi tasiyan kod dosyasi = 0 / 1, kodda nitelik listesi = 0
tur verisi  : 3 tur, tip/tur-verisi.mjs veri dosyasidir

yeni tur "belge" eklendi (1 veri satiri, 0 kod satiri)
  sinif tabanli -> hata: bilinmeyen hizmet turu: belge
  tip nesnesi   -> 6048, katalogdaki tur sayisi = 4
  soguk-zincir  tip nesnesi: eksik nitelik: sicaklik  | sinif tabanli: 13824
  degerli       tip nesnesi: taninmayan nitelik: sicaklik  | sinif tabanli: 11300
  standart      tip nesnesi: taninmayan nitelik: sayfa  | sinif tabanli: 8640
dogrulama: tip nesnesi 3/3, sinif tabanli 0/3
```

İlk iki satır kazancı veriyor: sınıf tabanlı düzende iki kod dosyasının ikisi de tür adı
taşıyor ve nitelik listesi kodda üç yerde duruyor. Tip nesnesi düzeninde tür adı taşıyan kod
dosyası 0, kodda nitelik listesi 0; üçünün üçü de veriye geçmiş durumda.

Dördüncü tür bunun sonucudur. `belge` türü koda hiç girmeden eklendi: 1 veri satırı, 0 kod
satırı. Sınıf tabanlı düzen aynı gönderiyi ücretlendiremedi ve bilinmeyen tür hatası verdi.
Katalog dört türe çıktı ve yeni türün ücreti hesaplandı.

Son üç satır bedeli gösteriyor. Sınıf tabanlı düzen üç hatalı gönderinin üçünü de sessizce
ücretlendirdi: eksik nitelikli soğuk zincir gönderisi 13824, tanınmayan nitelik taşıyan iki
gönderi de bir tutar üretti. Tip nesnesi üçünü de yakaladı, ama bunu **çalışma zamanında** ve
kendi yazdığı doğrulamayla yaptı.

#### Bedel ve Ne Zaman Uygulanmaz

Tip nesnesinin ödediği şey yazım zamanında var olan denetimdir. Sınıf tabanlı düzende bir tür
için yanlış bir nitelik adı yazıldığında hata sınıfın metninde görünür; tip nesnesi düzeninde
nitelik adları dizgeye dönüşür ve hiçbir okuma sırasında denetlenmez. Bu yüzden kalıp bir
doğrulama yükümlülüğü getirir: yukarıdaki ölçümde üç bulgu doğrulama işlevi yazıldığı için
yakalandı, kalıbın kendisi yakalamadı.

İkinci bedel çağrı yerinin belirsizleşmesidir. Sınıf tabanlı düzende bir türün ücret
hesabını okumak için o türün sınıfına gidilir; tip nesnesi düzeninde tek bir `ucret` gövdesi
bütün türleri hesaplar ve türe özgü davranış `ek` gibi alanlarla ifade edilir. Türe özgü
davranış karmaşıklaştıkça bu alanların sayısı artar ve tip nesnesi kendi içinde küçük bir
yorumlayıcıya dönüşmeye başlar.

Buradan uygulanma sınırı çıkar. Tür sayısı sabit ve azsa, her türün davranışı gerçekten farklı
bir algoritmaysa, kalıp karşılıksızdır: nitelik listesi zaten 3 yerde durur ve yeni tür
gelmediği için düzenlenen dosya sayısı 0 kalır. Bu durumda türü çok biçimlilikle ifade etmek
hem yazım zamanı denetimini korur hem davranışı kendi dosyasında tutar.

Ters koşullar kalıbı gerektirir: tür kümesi çalışma zamanında geliyorsa, kullanıcı yeni tür
tanımlıyorsa, ya da türler yalnız veriyle ayrışıp davranışları ortak bir kuralın
parametreleriyse. Ölçüt tek cümledir: türler arasındaki fark **veriyle** ifade edilebiliyorsa
tip nesnesi, **kodla** ifade edilmek zorundaysa çok biçimlilik.

#### Özet

- Tip nesnesi türü bir sınıf olarak değil bir nesne olarak modeller; nitelik varyantları
  türe göre değişen alanları bir nitelik torbasında toplar ve geçerliliğini tip nesnesine
  sorar.
- Sınıf tabanlı düzende iki kod dosyasının ikisi de tür adı taşıyordu ve nitelik listesi kodda
  3 yerde duruyordu; tip nesnesi düzeninde ikisi de 0'a indi.
- Dördüncü tür 1 veri satırı ve 0 kod satırıyla eklendi ve ücreti hesaplandı; sınıf tabanlı
  düzen aynı gönderi için bilinmeyen tür hatası verdi.
- Bedel yazım zamanı denetiminin kaybıdır: üç hatalı gönderiyi sınıf tabanlı düzen sessizce
  ücretlendirdi, tip nesnesi üçünü de yakaladı ama bunu elle yazılmış bir doğrulamayla yaptı.
- Ölçüt türler arasındaki farkın nerede ifade edilebildiğidir: veriyle ifade edilebiliyorsa
  tip nesnesi, kodla ifade edilmek zorundaysa çok biçimlilik.

#### Kurs Kapanışı

Kurs dört problem sınıfını dolaştı. **Yaratımsal kalıplar** nesnenin nasıl ve nerede
yaratıldığını sordu: Kalıp Kavramı dersi kalıbın anlatısını problem, çözüm ve sonuçlar
üçlüsü olarak kurdu ve ilk ölçümü verdi — tür adının geçtiği yer sayısı 6'dan 2'ye inerken
dosya sayısı 3'ten 5'e, ithal kenarı sayısı 2'den 7'ye çıktı. **Yapısal kalıplar** nesnelerin
nasıl birleştirildiğini ve uyumsuz arayüzlerin nasıl köprülendiğini sordu; Bağdaştırıcı dersi
kazancı dış sözlüğe dokunan dosya oranıyla, bedeli 3 dosya, 4 satır ve bir dolaylılık
basamağıyla saydı. **Davranışsal kalıplar** sorumluluğun nesneler arasında nasıl dağıtıldığını
sordu; Strateji dersinde dördüncü tarife gömülü sürümde 2 dosya ve 13 satır düzenlemeye mal
olurken strateji sürümünde 0 dosya düzenlemesiyle eklendi, karşılığında izlenmesi gereken
gövde sayısı 1'den 3'e çıktı.

**Kurumsal uygulama kalıpları** ölçeği bir uygulamanın katman düzenine çıkardı. Aynı ölçüm
biçimi orada da yürüdü: kural uygulama noktası 3'ten 1'e, alan modülündeki kalıcılık izi
dört, iki ve beş adımdan 0'a, uygulama katmanındaki tekrarlanan senaryo adımı 7'den 0'a, iç
model değiştiğinde bozulan istemci sayısı 3'ten 0'a, tür adı taşıyan kod dosyası 2'den 0'a
indi. Her derste bedel de aynı cümleyle sayıldı: eklenen dosya, eklenen ad, dolaylılık
basamağı, izlenmesi gereken çağrı.

Kursun başında kurulan iddia budur: **kalıp, ölçüyü iyileştirdiği ölçüde kalıptır.** Otuz
dersin hiçbirinde kalıp kendiliğinden iyi sayılmadı; her biri bir sayıyı düşürdüğü için
seçildi ve her birinin "ne zaman uygulanmaz" bölümü aynı sayının nasıl sıfırlandığını
gösterdi. Kazanç ile bedelin oranı bağlama bağlıdır ve bağlam ölçülebilir: kaç senaryo aynı
kurala dokunuyor, kaç istemci aynı sınırdan geçiyor, kaç tür bekleniyor. Bu sorular
yanıtlanmadan bir kalıp seçilemez.

Kalıplar bir soruyu açıkta bıraktı. Hepsi çözümün **biçimi** üzerineydi: kural nereye yazılır,
nesne nasıl yaratılır, sınır nereye çizilir. Hiçbiri kuralın **ne olduğunu** sormadı. Gönderi,
tarife, bölge, rota ve indirim sözcüklerinin ne anlama geldiği, hangilerinin bir arada
değiştiği, aynı sözcüğün iki takımda iki farklı şeyi adlandırıp adlandırmadığı bu kursun
dışında kaldı. Sonraki kurs **Alan Odaklı Tasarım** tam bu soruyu alır: alan dilinin ve
sınırlı bağlamların birer modelleme aracı hâline gelmesi, yani bir kod tabanının yapısının
alanın kendi ayrımlarından türetilmesi.

