---
title: 'Programlama Paradigmaları'
source: 'https://academia.sh/tr/kurslar/paradigmalar'
language: tr
updated: '2026-08-17T04:42:41+00:00'
license: 'CC BY-SA 4.0'
lessons: 14
outcomes:
    - 'Nesneye dayalı ve fonksiyonel yaklaşımların modelleme farkını açıklar'
    - 'Kalıtım yerine bileşim seçilmesi gereken durumları ayırt eder'
    - 'Bir problemi birden çok paradigmayla modelleyip karşılaştırır'
---

# Programlama Paradigmaları

Bir programın hangi birimlere bölündüğünü belirleyen model kararını ele alan kurs: yapısal ve nesneye dayalı düzenlemenin araçları, fonksiyonel modelin kurduğu farklı birim, kalıtım ile bileşim arasındaki seçim ve aynı problemin birden çok paradigmayla modellenip ölçülerek karşılaştırılması.

## Yapısal ve Nesneye Dayalı Programlama

Denetim akışının disipline edilmesinden nesne sorumluluğuna uzanan araç kümesi: kapsülleme, soyutlama, kalıtım ve çok biçimliliğin seçim ölçütleri, arayüz ile soyut sınıf arasındaki karar, kırılgan üst sınıf sorunu ve her tercihin ölçülen bedeli.

### Yapısal Programlama

Source: https://academia.sh/tr/kurslar/paradigmalar/yapisal-programlama

Paradigmanın bir sözdizimi değil bir bölme kararı olduğunun gösterilmesi: aynı ücretlendirme akışının bayraklı ve bölünmüş iki sürümünde bağımsız yol sayısının, çıkış noktalarının ve dal kapsamı için gereken durum sayısının ölçülmesi.

Önceki kurs, davranışı koruyarak yapıyı değiştirmenin güvencesini kurdu: testler yeşilken
ayıklama, taşıma ve yeniden adlandırma yapılabiliyordu. O kurs boyunca verilen her karar
ortak bir varsayımı paylaşıyordu. Kodun nasıl yazıldığı sorgulandı; hangi modelle
düşünüldüğü sorgulanmadı. Bir fonksiyonun uzunluğu, bir adın açıklığı, bir yinelemenin
gereksizliği tartışıldı — ama programın **fonksiyonlardan** oluştuğu hiç tartışılmadı.

Bu kurs o varsayımı açar. Bir **paradigma** (paradigm) bir sözdizimi değildir; bir
programın hangi birimlere bölüneceğini belirleyen bir karardır. Birim yordam olabilir,
durum taşıyan bir nesne olabilir, girdiden çıktıya bir eşleme olabilir. Seçim, kodun
görünüşünü değil, değişikliğin nereye kadar yayıldığını belirler. Yapısal programlama bu
kararların ilkidir ve diğerlerinin üzerine kurulduğu zemindir.

#### Bölme Kararı Nerede Verilir

Programlama Temelleri kursunda yapısal programlamanın üç yapısı tanıtıldı: sıra, seçim ve
yineleme. Bu üçünün her hesaplanabilir programı yazmaya yettiği biçimsel bir sonuçtur.
Buradaki soru yeterlilik değil, **disiplin**: aynı üç yapıyla yazılmış iki program,
birimlere bölünüş biçimleri yüzünden çok farklı bakım maliyeti taşır.

Ayrımı görünür kılan ölçüt, bir birime kaç ayrı yoldan girilebildiğidir. **Tek giriş–tek
çıkış** (single entry, single exit) kısıtı, bir bloğa yalnız başından girilmesini ve
yalnız sonundan çıkılmasını söyler. Kısıtın değeri gizemli değil, aritmetiktir: bir kod
noktasındaki değerler hakkında akıl yürütmek için oraya varan tüm yolların incelenmesi
gerekir ve yol sayısı her karar noktasında katlanır.

Erken çıkış — koruma cümlesi, döngüden çıkma — bu kısıtın denetimli gevşetilmesidir; hedefi
sabittir. Denetimsiz olan, akışın bayrak değişkenleriyle yönlendirilmesidir: bayrak, bir
kararın sonucunu kodun ilerideki bir noktasına taşır ve iki nokta arasındaki her satır o
kararın etkisi altına girer.

#### Aynı Akışın İki Sürümü

Bu kurs boyunca aynı kod tabanı geliştirilecek: bir gönderi ücretlendirme ve yönlendirme
kitaplığı. Bir `gonderi`, ağırlık, hacim, teslim ili, beyan edilen değer ve sözleşmeli
müşteri bilgisi taşır. Bir `tarife`, ağırlık kademelerini, bölge katsayılarını ve asgari
ücreti tutar. Ücret, doğrulama ile başlayan ve asgari ücret alt sınırıyla biten bir
zincirin sonucudur.

İlk sürüm bu zinciri tek gövdede, bayraklarla yazar.

```js
// ucret-bayrakli.mjs — ucretlendirme akisinin tek govdede, bayraklarla yazilmis surumu
export const BOLGELER = { 34: 1, 6: 1, 35: 2, 1: 2, 21: 3, 65: 3 };

export function ucretHesapla(gonderi, tarife) {
  let hata = null;
  let gecersiz = false;
  if (gonderi.agirlikGram <= 0) { hata = "agirlik pozitif olmali"; gecersiz = true; }
  if (!gecersiz && gonderi.hacimCm3 <= 0) { hata = "hacim pozitif olmali"; gecersiz = true; }
  if (!gecersiz && BOLGELER[gonderi.teslimIli] === undefined) {
    hata = "teslim ili bilinmiyor"; gecersiz = true;
  }
  if (gecersiz) return { durum: "reddedildi", sebep: hata };

  const hacimAgirligi = gonderi.hacimCm3 / 3;
  let ucretliAgirlik = gonderi.agirlikGram;
  if (hacimAgirligi > ucretliAgirlik) ucretliAgirlik = hacimAgirligi;

  let taban = 0;
  let bulundu = false;
  for (const [ustSinir, ucret] of tarife.kademeler) {
    if (!bulundu && ucretliAgirlik <= ustSinir) { taban = ucret; bulundu = true; }
  }
  if (!bulundu) {
    const son = tarife.kademeler[tarife.kademeler.length - 1];
    const fazlaKilo = Math.ceil((ucretliAgirlik - son[0]) / 1000);
    taban = son[1] + fazlaKilo * tarife.kiloBasiEk;
  }

  const bolge = BOLGELER[gonderi.teslimIli];
  let ucret = Math.round(taban * tarife.bolgeKatsayisi[bolge]);

  if (gonderi.deger > 100000) ucret += Math.round(gonderi.deger * 0.01);
  if (gonderi.sozlesmeli === true) ucret = Math.round(ucret * 0.9);
  if (ucret < tarife.asgariUcret) ucret = tarife.asgariUcret;

  return { durum: "hesaplandi", bolge, ucretliAgirlik, ucret };
}
```

İki bayrak var: `gecersiz` ve `bulundu`. İkisi de bir kararın sonucunu ileriye taşıyor.
`gecersiz` bayrağı üç ayrı `if` tarafından yazılıyor ve üçü de aynı değişkeni okuyor;
`bulundu` bayrağı döngünün içinde yazılıp döngüden sonra okunuyor.

İkinci sürüm aynı zinciri adımlarına böler. Her adım tek girişlidir ve kendi kararını
kendi içinde bitirir.

```js
// ucret-yapisal.mjs — ayni akis, her birimi tek girisli ve tek cikisli yordamlara bolunmus
export const BOLGELER = { 34: 1, 6: 1, 35: 2, 1: 2, 21: 3, 65: 3 };

export function dogrula(gonderi) {
  let sebep = null;
  if (gonderi.agirlikGram <= 0) sebep = "agirlik pozitif olmali";
  else if (gonderi.hacimCm3 <= 0) sebep = "hacim pozitif olmali";
  else if (BOLGELER[gonderi.teslimIli] === undefined) sebep = "teslim ili bilinmiyor";
  return sebep;
}

export function ucretliAgirlik(gonderi) {
  return Math.max(gonderi.agirlikGram, gonderi.hacimCm3 / 3);
}

export function kademeUcreti(agirlik, tarife) {
  const kademe = tarife.kademeler.find(([ustSinir]) => agirlik <= ustSinir);
  if (kademe !== undefined) return kademe[1];
  const [sonSinir, sonUcret] = tarife.kademeler[tarife.kademeler.length - 1];
  return sonUcret + Math.ceil((agirlik - sonSinir) / 1000) * tarife.kiloBasiEk;
}

export function bolgeyeUyarla(taban, bolge, tarife) {
  return Math.round(taban * tarife.bolgeKatsayisi[bolge]);
}

export function eklentileriUygula(ucret, gonderi, tarife) {
  let sonuc = ucret;
  if (gonderi.deger > 100000) sonuc += Math.round(gonderi.deger * 0.01);
  if (gonderi.sozlesmeli === true) sonuc = Math.round(sonuc * 0.9);
  return Math.max(sonuc, tarife.asgariUcret);
}

export function ucretHesapla(gonderi, tarife) {
  const sebep = dogrula(gonderi);
  if (sebep !== null) return { durum: "reddedildi", sebep };
  const bolge = BOLGELER[gonderi.teslimIli];
  const agirlik = ucretliAgirlik(gonderi);
  const taban = kademeUcreti(agirlik, tarife);
  const ucret = eklentileriUygula(bolgeyeUyarla(taban, bolge, tarife), gonderi, tarife);
  return { durum: "hesaplandi", bolge, ucretliAgirlik: agirlik, ucret };
}
```

Her iki bayrak da kayboldu. `gecersiz`, `dogrula` yordamının dönüş değerine dönüştü;
`bulundu`, bir arama işleminin `undefined` sonucuna. Bayrak, adı olmayan bir dönüş
değeridir; ada kavuştuğunda değişken olmaktan çıkar.

#### Bölme Davranışı Değiştirmedi

İddianın ilk yarısı, iki sürümün aynı işi yaptığıdır. Bu ölçülür.

```js
// esdegerlik.mjs — iki surumun ayni girdi izgarasinda ayni sonucu verdigini dogrular
import assert from "node:assert/strict";
import { ucretHesapla as bayrakli } from "./ucret-bayrakli.mjs";
import { ucretHesapla as yapisal } from "./ucret-yapisal.mjs";

export const TARIFE = {
  kademeler: [[1000, 4500], [5000, 7000], [10000, 11000]],
  kiloBasiEk: 1800,
  bolgeKatsayisi: { 1: 1, 2: 1.25, 3: 1.6 },
  asgariUcret: 5000,
};

const izgara = [];
for (const agirlikGram of [500, 4000, 12000])
  for (const hacimCm3 of [900, 30000])
    for (const teslimIli of [34, 35, 21, 99])
      for (const deger of [0, 500000])
        for (const sozlesmeli of [false, true])
          izgara.push({ agirlikGram, hacimCm3, teslimIli, deger, sozlesmeli });

let ayrilan = 0;
for (const gonderi of izgara) {
  try {
    assert.deepEqual(bayrakli(gonderi, TARIFE), yapisal(gonderi, TARIFE));
  } catch {
    ayrilan += 1;
  }
}
const sonuclar = new Set(izgara.map((g) => JSON.stringify(yapisal(g, TARIFE))));
console.log(`girdi sayisi        = ${izgara.length}`);
console.log(`ayrilan sonuc       = ${ayrilan}`);
console.log(`farkli sonuc sinifi = ${sonuclar.size}`);
```

```sh
node esdegerlik.mjs
```

```
girdi sayisi        = 96
ayrilan sonuc       = 0
farkli sonuc sinifi = 48
```

Doksan altı girdinin hiçbirinde ayrılma yok. Karşılaştırma bundan sonra davranış üzerinden
değil, yapı üzerinden yapılabilir.

#### Bağımsız Yol Sayısı

Bir yordamın **bağımsız yol** (independent path) sayısı, karar noktası sayısının bir
fazlasıdır; bu ölçüte **çevrimsel karmaşıklık** (cyclomatic complexity) denir. Sayı, o
yordamın tüm dallarını geçmek için gereken en az yol adedini verir.

```js
// olcum.mjs — her yordamin karar noktalarini sayar ve son karara ulasma bedelini bildirir
import { readFileSync } from "node:fs";

const KARAR = /\b(if|for|while|case|catch)\b|&&|\|\||\?\?/g;

function yordamlar(kaynak) {
  const bulunan = [];
  const baslik = /(?:export\s+)?function\s+(\w+)\s*\(/g;
  let m;
  while ((m = baslik.exec(kaynak)) !== null) {
    const bas = kaynak.indexOf("{", m.index);
    let derinlik = 0;
    let son = bas;
    for (; son < kaynak.length; son += 1) {
      if (kaynak[son] === "{") derinlik += 1;
      else if (kaynak[son] === "}") { derinlik -= 1; if (derinlik === 0) break; }
    }
    const govde = kaynak.slice(bas, son + 1);
    bulunan.push({
      ad: m[1],
      karar: (govde.match(KARAR) ?? []).length,
      cikis: (govde.match(/\breturn\b/g) ?? []).length,
    });
  }
  return bulunan;
}

for (const dosya of ["ucret-bayrakli.mjs", "ucret-yapisal.mjs"]) {
  const liste = yordamlar(readFileSync(dosya, "utf8"));
  console.log(dosya);
  for (const y of liste) {
    console.log(`  ${y.ad.padEnd(18)} bagimsiz yol=${y.karar + 1}  cikis=${y.cikis}`);
  }
  console.log(`  yordam=${liste.length}` +
    `  en buyuk birim=${Math.max(...liste.map((y) => y.karar + 1))}` +
    `  son karar oncesi saglanacak karar=${Math.max(...liste.map((y) => y.karar - 1))}`);
}
```

```sh
node olcum.mjs
```

```
ucret-bayrakli.mjs
  ucretHesapla       bagimsiz yol=15  cikis=2
  yordam=1  en buyuk birim=15  son karar oncesi saglanacak karar=13
ucret-yapisal.mjs
  dogrula            bagimsiz yol=4  cikis=1
  ucretliAgirlik     bagimsiz yol=1  cikis=1
  kademeUcreti       bagimsiz yol=2  cikis=2
  bolgeyeUyarla      bagimsiz yol=1  cikis=1
  eklentileriUygula  bagimsiz yol=3  cikis=1
  ucretHesapla       bagimsiz yol=2  cikis=2
  yordam=6  en buyuk birim=4  son karar oncesi saglanacak karar=2
```

Toplam karar sayısı ikisinde de yakın: 15'e karşı 13. Bölme, kararları yok etmedi;
onları **dağıttı**. Değişen, tek bir birimde yoğunlaşan miktar: 15'ten 4'e indi.

Son satırdaki ikinci sayı daha belirleyici. Bayraklı sürümde en son karara — asgari ücret
alt sınırına — ulaşan bir çalıştırma, kendisinden önceki 13 kararın sonucunu taşıyarak
oraya varır. Bölünmüş sürümde aynı karar `eklentileriUygula` yordamının içindedir ve
önünde yalnız 2 karar vardır. Bu fark, sonraki bölümün konusudur.

Çıkış sayısı sütunu, tek çıkış kısıtının nerede gevşetildiğini de gösterir. `kademeUcreti`
ve `ucretHesapla` ikişer çıkışa sahiptir; ikisi de koruma cümlesidir ve hedefleri
yordamın sonudur. Bayraklı sürümde de iki çıkış vardır, ama aralarındaki 30 satır iki
bayrağın etkisi altındadır.

#### Dal Kapsamı İçin Gereken Durum Sayısı

Ölçütün bakıma yansıyan karşılığı, **dal kapsamı** (branch coverage) için kaç durum
kurulması gerektiğidir. Bayraklı sürümün tek girişi vardır; her dala ancak eksiksiz bir
`gonderi` nesnesi kurularak ulaşılır.

```js
// bayrakli.test.mjs — tek govdeli surumun her dalina uctan uca gonderi kurarak ulasilir
import { test } from "node:test";
import assert from "node:assert/strict";
import { ucretHesapla } from "./ucret-bayrakli.mjs";

const TARIFE = {
  kademeler: [[1000, 4500], [5000, 7000], [10000, 11000]],
  kiloBasiEk: 1800,
  bolgeKatsayisi: { 1: 1, 2: 1.25, 3: 1.6 },
  asgariUcret: 5000,
};
const G = (o) => ({ agirlikGram: 4000, hacimCm3: 900, teslimIli: 34, deger: 0, sozlesmeli: false, ...o });

test("agirlik sifir", () => assert.equal(ucretHesapla(G({ agirlikGram: 0 }), TARIFE).durum, "reddedildi"));
test("hacim sifir", () => assert.equal(ucretHesapla(G({ hacimCm3: 0 }), TARIFE).durum, "reddedildi"));
test("bilinmeyen il", () => assert.equal(ucretHesapla(G({ teslimIli: 99 }), TARIFE).durum, "reddedildi"));
test("hacim agirligi baskin", () => assert.equal(ucretHesapla(G({ hacimCm3: 30000 }), TARIFE).ucretliAgirlik, 10000));
test("kademe disi agirlik", () => assert.equal(ucretHesapla(G({ agirlikGram: 12000 }), TARIFE).ucret, 14600));
test("degerli gonderi", () => assert.equal(ucretHesapla(G({ deger: 500000 }), TARIFE).ucret, 12000));
test("sozlesmeli musteri", () => assert.equal(ucretHesapla(G({ sozlesmeli: true }), TARIFE).ucret, 6300));
test("asgari ucret devreye girer", () => assert.equal(ucretHesapla(G({ agirlikGram: 100 }), TARIFE).ucret, 5000));
```

Bölünmüş sürümde her birim doğrudan çağrılabilir. Girdi, çoğu durumda bir sayıdır.

```js
// yapisal.test.mjs — bolunmus surumun dallari, birimler dogrudan cagrilarak kapsanir
import { test } from "node:test";
import assert from "node:assert/strict";
import { dogrula, ucretliAgirlik, kademeUcreti, bolgeyeUyarla, eklentileriUygula, ucretHesapla }
  from "./ucret-yapisal.mjs";

const TARIFE = {
  kademeler: [[1000, 4500], [5000, 7000], [10000, 11000]],
  kiloBasiEk: 1800,
  bolgeKatsayisi: { 1: 1, 2: 1.25, 3: 1.6 },
  asgariUcret: 5000,
};

test("dogrula dort dali", () => {
  assert.equal(dogrula({ agirlikGram: 0 }), "agirlik pozitif olmali");
  assert.equal(dogrula({ agirlikGram: 500, hacimCm3: 0 }), "hacim pozitif olmali");
  assert.equal(dogrula({ agirlikGram: 500, hacimCm3: 900, teslimIli: 99 }), "teslim ili bilinmiyor");
  assert.equal(dogrula({ agirlikGram: 500, hacimCm3: 900, teslimIli: 34 }), null);
});

test("ucretli agirlik iki dali", () => {
  assert.equal(ucretliAgirlik({ agirlikGram: 4000, hacimCm3: 900 }), 4000);
  assert.equal(ucretliAgirlik({ agirlikGram: 4000, hacimCm3: 30000 }), 10000);
});

test("kademe ucreti iki dali", () => {
  assert.equal(kademeUcreti(4000, TARIFE), 7000);
  assert.equal(kademeUcreti(12000, TARIFE), 14600);
});

test("bolgeye uyarlama", () => assert.equal(bolgeyeUyarla(7000, 3, TARIFE), 11200));

test("eklentiler uc dali", () => {
  assert.equal(eklentileriUygula(7000, { deger: 500000, sozlesmeli: false }, TARIFE), 12000);
  assert.equal(eklentileriUygula(7000, { deger: 0, sozlesmeli: true }, TARIFE), 6300);
  assert.equal(eklentileriUygula(4500, { deger: 0, sozlesmeli: false }, TARIFE), 5000);
});

test("akis iki dali", () => {
  assert.equal(ucretHesapla({ agirlikGram: 0 }, TARIFE).durum, "reddedildi");
  assert.equal(ucretHesapla(
    { agirlikGram: 4000, hacimCm3: 900, teslimIli: 34, deger: 0, sozlesmeli: false }, TARIFE).ucret, 7000);
});
```

Aşağıdaki komut her iki kümeyi de kapsam ölçümüyle çalıştırır ve yalnız sayı satırlarını
gösterir; süre alanları her koşumda değiştiği için ayıklanmıştır.

```sh
for f in bayrakli.test.mjs yapisal.test.mjs; do
  node --test --experimental-test-coverage $f 2>&1 |
    grep -E '^ℹ (tests|pass|fail) |^ℹ (file|ucret-|all files)'
done
```

```
ℹ tests 8
ℹ pass 8
ℹ fail 0
ℹ file               | line % | branch % | funcs % | uncovered lines
ℹ ucret-bayrakli.mjs | 100.00 |   100.00 |  100.00 |
ℹ all files          | 100.00 |   100.00 |  100.00 |
ℹ tests 6
ℹ pass 6
ℹ fail 0
ℹ file              | line % | branch % | funcs % | uncovered lines
ℹ ucret-yapisal.mjs | 100.00 |   100.00 |  100.00 |
ℹ all files         | 100.00 |   100.00 |  100.00 |
```

İki sürüm de tam dal kapsamına ulaşıyor. Fark, kapsamı elde etmenin bedelinde. Bayraklı
sürüm için sekiz durum kuruldu ve **sekizinin de** beş alanlı eksiksiz bir `gonderi`
nesnesine ihtiyacı var; yardımcı `G` işlevi tam da bu tekrarı gizlemek için yazıldı.
Bölünmüş sürümde on dört doğrulama var ve bunlardan yalnız **biri** eksiksiz bir gönderi
kuruyor; geri kalanı iki alanlı nesnelerle ya da doğrudan sayılarla çalışıyor.

Bedelin asıl görüneceği yer, kuralın değişmesidir. Asgari ücret kuralı değiştiğinde
bayraklı sürümün ilgili durumları, o karara ulaşmak için gereken 13 önceki kararı hâlâ
sağlamak zorundadır; bölünmüş sürümde `eklentileriUygula` için yazılmış üç satır yeterlidir.

#### Yapısal Bölmenin Bıraktığı Boşluk

Bölünmüş sürüm bir şeyi çözmedi. `dogrula` yordamı gönderinin geçerli olduğunu söyler, ama
bu bilgi hiçbir yerde saklanmaz. `ucretliAgirlik`, `kademeUcreti` ve `eklentileriUygula`
yordamlarının her biri, kendisine verilen gönderinin doğrulanmış olduğunu **varsayar**.
Varsayım koda yazılmamıştır; çağrı sırasına dayanır.

Aynı gönderi nesnesi doğrulamadan geçirilmeden `eklentileriUygula` yordamına verilirse,
hiçbir şey engellemez. Negatif ağırlıklı bir gönderi de bir ücret üretir. Kural — "ücret
hesaplanan her gönderi doğrulanmıştır" — bir **değişmez** (invariant) olmasına rağmen
kodun hiçbir noktasında korunmuyor; yalnız `ucretHesapla` yordamının içindeki sıralamada
yaşıyor.

Veri ile onu işleyen yordamların ayrı durması, yapısal bölmenin tanımı gereğidir. Bu ayrım
küçük programlarda sade, değişmez taşıyan verilerde ise korumasızdır. Sonraki dersin
konusu tam olarak bu boşluktur.

#### Özet

- Paradigma bir sözdizimi değil, programın hangi birimlere bölüneceğine dair bir karardır;
  yapısal yanıt yordamdır.
- Bayrak değişkeni, bir kararın sonucunu ileriye taşır ve aradaki her satırı o kararın
  etkisi altına sokar; adlandırılmış bir dönüş değeri aynı işi taşımadan yapar.
- Aynı akışın iki sürümü 96 girdinin tamamında aynı sonucu verdi; karşılaştırma davranış
  üzerinden değil yapı üzerinden yapıldı.
- Bölme toplam karar sayısını düşürmedi (15'e karşı 13); tek bir birimdeki yoğunluğu
  15'ten 4'e, son karara ulaşmak için sağlanacak önceki karar sayısını 13'ten 2'ye indirdi.
- İki sürüm de tam dal kapsamına ulaştı; bayraklı sürümde sekiz durumun sekizi de eksiksiz
  bir gönderi kurmak zorundayken bölünmüş sürümde on dört doğrulamanın yalnız biri kuruyor.
- Yapısal bölme, "ücret hesaplanan her gönderi doğrulanmıştır" değişmezini koruyamaz; bu
  kural yalnız çağrı sırasında yaşar.

#### Sonraki Adım

Değişmezin çağrı sırasına emanet edilmesi, onu koruyacak bir sahibi olmadığı anlamına
gelir. Sonraki ders, veriyi ve onu koruyan davranışı tek bir birimde toplayan aracı ele
alır: kapsülleme. Ölçüm de buna göre değişir — soru artık kaç yol olduğu değil, aynı işlem
dizisi altında kaç nesnenin bozuk duruma düştüğüdür.

### Kapsülleme

Source: https://academia.sh/tr/kurslar/paradigmalar/kapsulleme

Değişmezin sahibini belirleyen araç olarak kapsülleme: alanları açık ve kapalı iki sevkiyat sürümüne aynı işlem dizisinin uygulanması, bozuk duruma düşen nesne sayısının sayılması ve dönüş değeri kapsanmadığında korumanın nerede kırıldığı.

Önceki ders bölünmüş ücretlendirme akışının bir boşluk bıraktığını gösterdi: "ücret
hesaplanan her gönderi doğrulanmıştır" kuralı hiçbir yerde korunmuyor, yalnız
`ucretHesapla` yordamının içindeki çağrı sırasında yaşıyordu. Yordamlar veriyi argüman
olarak alır ve verinin geçmişi hakkında hiçbir güvence taşımaz.

**Kapsülleme** (encapsulation), bu boşluğu kapatan araçtır: veriyi ve onu değiştiren
işlemleri tek bir birimde toplar, veriye erişimi bu işlemlerle sınırlar. Programlama
Temelleri kursunda tanıtılan tanım budur. Bu derste tanım tekrarlanmaz; sorulan soru
farklıdır — kapsülleme ne zaman gerçekten çalışır, nerede kırılır ve bedeli nedir.

#### Kapsülleme Neyi Kapatır

Yaygın bir yanlış okuma, kapsüllemenin "tüm alanları gizlemek" olduğudur. Ölçüt gizlilik
değil, **değişmez** (invariant). Bir alan, bir değişmezin denklemine giriyorsa dışarıdan
yazılamamalıdır; girmiyorsa gizlenmesi bir şey kazandırmaz.

Bu kursun kitaplığında bir `sevkiyat`, bir araca yüklenen gönderiler kümesidir ve üç
değişmez taşır:

1. Kayıtlı toplam ağırlık, yüklü gönderilerin ağırlık toplamına eşittir.
2. Toplam ağırlık, aracın kapasitesini aşmaz.
3. Mühürlenmiş bir sevkiyata gönderi eklenmemiştir.

`kapasiteGram` ikinci değişmezin bir tarafıdır; dışarıdan yazılabilirse değişmez tek
atamayla çiğnenir. `aracId` ise hiçbir denklemde geçmez; onu gizlemek yalnız erişimci
yazdırır. Kapsülleme kararı alan alan verilir, sınıf toptan kapatılarak değil.

Değişmezlerin denetimi tek bir yerde yazılır ki iki sürüme de aynı ölçüt uygulanabilsin.
Aynı dosyada, koşumların yinelenebilir olması için sabit tohumlu bir sayı üreteci de
bulunuyor.

```js
// denetci.mjs — uc degismezi tek yerde tanimlar; iki surume de ayni denetim uygulanir
export function ihlaller(d) {
  const bozuk = [];
  const toplam = d.gonderiler.reduce((t, g) => t + g.agirlikGram, 0);
  if (toplam !== d.toplamAgirlik) bozuk.push("toplam-tutarsiz");
  if (d.toplamAgirlik > d.kapasiteGram) bozuk.push("kapasite-asildi");
  if (d.muhurlu && d.gonderiler.length > d.muhurdekiSayi) bozuk.push("muhur-sonrasi-ekleme");
  return bozuk;
}

export function uretec(tohum) {
  let x = tohum;
  return () => (x = (x * 1103515245 + 12345) % 2147483648) / 2147483648;
}
```

#### Alanları Açık Sürüm

İlk sürüm sevkiyatı düz bir nesne olarak tutar ve kuralları bir `ekle` yordamına yazar.
Kurallar burada **vardır**; eksik olan, onlardan geçmenin zorunlu olmasıdır.

```js
// sevkiyat-acik.mjs — alanlari disariya acik sevkiyat; kurallar ekle() icinde ama zorunlu degil
export function sevkiyatOlustur(aracId, kapasiteGram) {
  return { aracId, kapasiteGram, gonderiler: [], toplamAgirlik: 0, muhurlu: false, muhurdekiSayi: 0 };
}

export function ekle(sevkiyat, gonderi) {
  if (sevkiyat.muhurlu) return false;
  if (sevkiyat.toplamAgirlik + gonderi.agirlikGram > sevkiyat.kapasiteGram) return false;
  sevkiyat.gonderiler.push(gonderi);
  sevkiyat.toplamAgirlik += gonderi.agirlikGram;
  return true;
}

export function muhurle(sevkiyat) {
  sevkiyat.muhurlu = true;
  sevkiyat.muhurdekiSayi = sevkiyat.gonderiler.length;
}
```

Bu tasarımda `sevkiyat.gonderiler.push(g)` yazmak, `ekle(sevkiyat, g)` yazmaktan kısadır
ve aynı görünen sonucu üretir. Büyüyen bir kod tabanında bu kısa yol er geç kullanılır:
bir aktarma adımında, bir içe aktarma betiğinde, bir hata düzeltmesinde. Kısa yolun
sonucu, atlanan iki denetimdir.

#### Alanları Kapalı Sürüm

İkinci sürümde değişmeze giren dört alan özeldir. Dışarıdan görünen tek yol iki yöntemdir.

```js
// sevkiyat-kapali.mjs — degismeze giren alanlar ozel; her degisiklik iki yontemden gecer
export class Sevkiyat {
  #kapasiteGram;
  #gonderiler = [];
  #toplamAgirlik = 0;
  #muhurlu = false;
  #muhurdekiSayi = 0;

  constructor(aracId, kapasiteGram) {
    if (kapasiteGram <= 0) throw new RangeError("kapasite pozitif olmali");
    this.aracId = aracId;
    this.#kapasiteGram = kapasiteGram;
  }

  ekle(gonderi) {
    if (this.#muhurlu) return false;
    if (this.#toplamAgirlik + gonderi.agirlikGram > this.#kapasiteGram) return false;
    this.#gonderiler.push({ ...gonderi });
    this.#toplamAgirlik += gonderi.agirlikGram;
    return true;
  }

  muhurle() {
    this.#muhurlu = true;
    this.#muhurdekiSayi = this.#gonderiler.length;
  }

  durum() {
    return {
      kapasiteGram: this.#kapasiteGram,
      gonderiler: this.#gonderiler.map((g) => ({ ...g })),
      toplamAgirlik: this.#toplamAgirlik,
      muhurlu: this.#muhurlu,
      muhurdekiSayi: this.#muhurdekiSayi,
    };
  }
}
```

`aracId` açık kaldı: hiçbir değişmezin parçası değil. `ekle` yönteminin içindeki
`{ ...gonderi }` bir **savunma kopyasıdır**; çağıranın elinde kalan nesne sonradan
değiştirilse bile sevkiyatın toplamı bozulmaz. `durum()` de aynı gerekçeyle kopya
döndürür — bu kararın bedeli dersin ilerleyen bölümünde ölçülecek.

#### Aynı İşlem Dizisi, İki Sonuç

Ölçüm şu biçimde kurulur: sabit tohumlu bir üreteçle 200 sevkiyat için 20'şer işlemlik bir
dizi üretilir; aynı dizi iki sürüme de uygulanır. Dizideki işlemlerin bir bölümü kısa
yoldur — açık sürümde doğrudan alan yazımına, kapalı sürümde ise `ekle` çağrısına karşılık
gelir, çünkü orada başka yol yoktur.

```js
// kosum.mjs — ayni islem dizisi iki surume uygulanir, bozuk duruma dusen nesne sayilir
import { sevkiyatOlustur, ekle, muhurle } from "./sevkiyat-acik.mjs";
import { Sevkiyat } from "./sevkiyat-kapali.mjs";
import { ihlaller, uretec } from "./denetci.mjs";

const NESNE = 200;
const ISLEM = 20;

function islemler() {
  const rast = uretec(20250729);
  const dizi = [];
  for (let n = 0; n < NESNE; n += 1) {
    const tek = [];
    for (let i = 0; i < ISLEM; i += 1) {
      const g = { id: `${n}-${i}`, agirlikGram: 100 + Math.floor(rast() * 3900) };
      const r = rast();
      if (r < 0.10) tek.push({ tur: "muhurle" });
      else if (r < 0.45) tek.push({ tur: rast() < 0.5 ? "kisayol-tam" : "kisayol-yarim", g });
      else tek.push({ tur: "ekle", g });
    }
    dizi.push(tek);
  }
  return dizi;
}

const dizi = islemler();

let acikBozuk = 0;
const acikIhlal = new Map();
for (const tek of dizi) {
  const s = sevkiyatOlustur("34ABC01", 20000);
  for (const op of tek) {
    if (op.tur === "muhurle") muhurle(s);
    else if (op.tur === "ekle") ekle(s, op.g);
    else if (op.tur === "kisayol-tam") { s.gonderiler.push(op.g); s.toplamAgirlik += op.g.agirlikGram; }
    else s.gonderiler.push(op.g);
  }
  const bozuk = ihlaller(s);
  if (bozuk.length > 0) acikBozuk += 1;
  for (const ad of bozuk) acikIhlal.set(ad, (acikIhlal.get(ad) ?? 0) + 1);
}

let kapaliBozuk = 0;
let reddedilen = 0;
for (const tek of dizi) {
  const s = new Sevkiyat("34ABC01", 20000);
  for (const op of tek) {
    if (op.tur === "muhurle") s.muhurle();
    else if (s.ekle(op.g) === false) reddedilen += 1;
  }
  if (ihlaller(s.durum()).length > 0) kapaliBozuk += 1;
}

console.log(`nesne sayisi          = ${NESNE}`);
console.log(`acik surum bozuk      = ${acikBozuk}`);
console.log(`kapali surum bozuk    = ${kapaliBozuk}`);
console.log(`kapali surum reddetti = ${reddedilen}`);
for (const [ad, sayi] of [...acikIhlal].sort()) console.log(`  ihlal ${ad.padEnd(21)} = ${sayi}`);
```

```sh
node kosum.mjs
```

```
nesne sayisi          = 200
acik surum bozuk      = 198
kapali surum bozuk    = 0
kapali surum reddetti = 2428
  ihlal kapasite-asildi       = 43
  ihlal muhur-sonrasi-ekleme  = 137
  ihlal toplam-tutarsiz       = 195
```

İki yüz sevkiyatın 198'i açık sürümde en az bir değişmezi çiğnedi. Kapalı sürümde bozuk
nesne yok — ama asıl bilgi son satırda: kapalı sürüm 2428 işlemi **reddetti**. Aynı 2428
işlem açık sürümde sessizce kabul edilmişti.

Bu, kapsüllemenin ne yapıp ne yapmadığını tam olarak söyler. Kapsülleme talebi ortadan
kaldırmaz; sessiz bozulmayı **görünür bir redde** çevirir. Reddin çağırana nasıl
bildirileceği ayrı bir tasarım kararıdır: burada `false` dönüldü, sebebi taşıyan bir sonuç
nesnesi de dönülebilirdi. Ölçülebilir olan, bozulmanın 198'den 0'a inmesidir.

#### Koruma Nerede Kırılır

Özel alan tek başına yetmez. Bir yöntem iç veriyi olduğu gibi döndürüyorsa, çağıran o
veriyi değiştirerek değişmezi dışarıdan bozabilir. Aşağıdaki sınıf alanları özel tutar ama
`manifesto()` iç diziyi doğrudan verir.

```js
// sizdiran-sevkiyat.mjs — alanlar ozel ama manifesto() ic diziyi oldugu gibi dondurur
export class SizdiranSevkiyat {
  #kapasiteGram; #gonderiler = []; #toplamAgirlik = 0;
  constructor(kapasiteGram) { this.#kapasiteGram = kapasiteGram; }
  ekle(gonderi) {
    if (this.#toplamAgirlik + gonderi.agirlikGram > this.#kapasiteGram) return false;
    this.#gonderiler.push({ ...gonderi });
    this.#toplamAgirlik += gonderi.agirlikGram;
    return true;
  }
  manifesto() { return this.#gonderiler; }
  kopyaManifesto() { return this.#gonderiler.map((g) => ({ ...g })); }
  durum() {
    return { kapasiteGram: this.#kapasiteGram, gonderiler: this.kopyaManifesto(),
             toplamAgirlik: this.#toplamAgirlik, muhurlu: false, muhurdekiSayi: 0 };
  }
}
```

Çağıran taraf, manifestoyu alıp aktarma parçasını listeye ekleyen sıradan bir adımdır.

```js
// sizinti.mjs — donus degeri kapsanmadiginda ozel alanin korumasi ne kadar dayanir
import { SizdiranSevkiyat } from "./sizdiran-sevkiyat.mjs";
import { ihlaller, uretec } from "./denetci.mjs";

function kosu(manifestoAl) {
  const rast = uretec(20250729);
  let bozuk = 0;
  for (let n = 0; n < 200; n += 1) {
    const s = new SizdiranSevkiyat(20000);
    for (let i = 0; i < 8; i += 1) {
      s.ekle({ id: `${n}-${i}`, agirlikGram: 100 + Math.floor(rast() * 3900) });
    }
    // aktarma adimi: manifestoyu alip aktarma parcasini listeye ekler
    manifestoAl(s).push({ id: `${n}-aktarma`, agirlikGram: 2500 });
    if (ihlaller(s.durum()).length > 0) bozuk += 1;
  }
  return bozuk;
}

console.log(`ic diziyi donduren manifesto  -> bozuk nesne = ${kosu((s) => s.manifesto())}`);
console.log(`kopya donduren manifesto      -> bozuk nesne = ${kosu((s) => s.kopyaManifesto())}`);
```

```sh
node sizinti.mjs
```

```
ic diziyi donduren manifesto  -> bozuk nesne = 200
kopya donduren manifesto      -> bozuk nesne = 0
```

İki yüz nesnenin tamamı bozuldu; tek fark, döndürülen değerin kopya olup olmamasıydı.
Kapsüllemenin sınırı erişim belirtecinde değil, **nesnenin dışına çıkan her referansta**
çizilir. Aynı gözlem yapıcıya giren referanslar için de geçerlidir; kapalı sürümün `ekle`
yönteminde kopya alınmasının nedeni buydu.

#### Bedel

Kopya döndürmek bedava değildir. `durum()` çağrısı, gönderi sayısıyla orantılı iş yapar;
sıcak bir döngüde çağrılırsa maliyet ölçülebilir hâle gelir. Üç yaygın çıkış yolu vardır:
değiştirilemez öge kullanmak (kopya gereksizleşir), salt okunur bir görünüm döndürmek, ya
da listeyi hiç döndürmeyip sorulan soruyu nesneye sormak — "kaç gönderi var", "toplam
ağırlık nedir".

İkinci bedel, kapsüllemenin yanlış yere uygulanmasıdır. Hiçbir değişmez taşımayan bir veri
taşıyıcısında — bir adres kaydı, bir tarife satırı — her alan için erişimci yazmak, açık
alanla aynı korumayı sıfır kazançla pahalıya alır. Ölçüt değişmedi: kapatılan şey bir
kuralı koruyorsa kapsülleme, korumuyorsa tören.

#### Özet

- Kapsüllemenin ölçütü gizlilik değil değişmezdir; bir alan bir değişmezin denklemine
  girmiyorsa gizlenmesi kazanç sağlamaz.
- Aynı 4000 işlemlik dizi altında alanları açık sürümde 200 sevkiyatın 198'i bozuk duruma
  düştü, kapalı sürümde hiçbiri düşmedi.
- Kapsülleme talebi yok etmez, sessiz bozulmayı görünür redde çevirir: kapalı sürüm 2428
  işlemi reddetti, açık sürüm aynı işlemleri sessizce kabul etmişti.
- İç veriyi olduğu gibi döndüren bir yöntem korumayı tümüyle kaldırır; iç diziyi döndüren
  sürümde 200 nesnenin 200'ü bozuldu, kopya döndürende hiçbiri bozulmadı.
- Kopyanın bedeli gönderi sayısıyla orantılıdır; alternatifleri değiştirilemez öge, salt
  okunur görünüm ve listeyi döndürmek yerine soruyu nesneye sormaktır.

#### Sonraki Adım

Kapsülleme, verinin dışarıdan yazılmasını engelledi. Ama sevkiyatın dışarıya kaç ad
açtığı, bu adların ne kadar iç ayrıntı sızdırdığı ve bir ayrıntı değiştiğinde kaç çağrı
noktasının etkilendiği henüz sorulmadı. Sonraki ders bu soruyu ele alır: bir modülün dışa
açtığı ad sayısı ile istemcinin bilmek zorunda olduğu ayrıntı sayısı ölçülür, temsil
değiştirildiğinde iki tasarımda kaç istemcinin kırıldığı sayılır.

### Soyutlama

Source: https://academia.sh/tr/kurslar/paradigmalar/soyutlama

Soyutlamanın ölçütü olarak açılan ad ile bilinen ayrıntının ayrılması: aynı tarifenin temsili açık ve temsili gizli iki sunumunda istemcilerin bildiği ayrıntının sayılması, temsil değiştiğinde kırılan istemci sayısının karşılaştırılması ve gizlemenin bedelinin ölçülmesi.

Önceki ders sevkiyatın alanlarını dışarıdan yazılamaz hâle getirdi ve bunun değişmezleri
nasıl koruduğunu saydı. O ölçüm bir soruyu açıkta bıraktı: bir modülün dışarıya **kaç ad**
açtığı ve bu adların ne kadar iç ayrıntı taşıdığı. Kapsülleme yazmayı engeller; okumayı ve
bağımlılığı engellemez.

**Soyutlama** (abstraction), bir birimin dışarıya yalnız kullanım için gerekli olanı
göstermesi, gerisini gizlemesidir. Programlama Temelleri kursunda tanıtılan tanım budur.
Bu derste sorulan soru, tanım değil ölçüttür: "gerekli olan" hangi sayıyla belirlenir ve
yanlış çizilen bir sınırın bedeli nedir.

#### Ölçüt: Açılan Ad ile Bilinen Ayrıntı Aynı Şey Değil

Yaygın ölçüt, dışa açılan ad sayısıdır — az ad açan modül daha iyi soyutlanmış sayılır. Bu
ölçüt tek başına yanıltıcıdır, çünkü tek bir ad da bir veri yapısının tüm iç biçimini
taşıyabilir. Bir tarife tablosunu tek bir nesne olarak dışa açan modül **bir** ad açar, ama
o nesneyi kullanan her istemci kademelerin nasıl saklandığını, sıralı olup olmadığını ve
alanların adlarını bilmek zorunda kalır.

İşe yarayan ölçüt ikilidir: **dışa açılan ad sayısı** ve **istemcinin bilmek zorunda olduğu
temsil ayrıntısı sayısı**. İkisi birlikte, üçüncü bir sayıyı öngörür — bir ayrıntı
değiştiğinde dokunulması gereken istemci sayısı. Bu ders üçünü de sayar.

#### Aynı Tarifenin İki Sunumu

Ücretlendirme kitaplığının tarife tablosu iki biçimde sunulur. İlkinde temsil dışarıdadır.

```js
// acik/tarife.mjs — temsil disariya acik: kademe dizisi, katsayi tablosu ve esikler okunabilir
export const TARIFE = {
  kademeler: [[1000, 4500], [5000, 7000], [10000, 11000]],
  kiloBasiEk: 1800,
  bolgeKatsayisi: { 1: 1, 2: 1.25, 3: 1.6 },
  asgariUcret: 5000,
};
```

Üç istemci bu tabloyu kullanır. İlki bir siparişin toplamını çıkarır ve ücreti kendisi
kurar.

```js
// acik/siparis.mjs — istemci kademe dizisinin bicimini bilerek ucreti kendisi kurar
import { TARIFE } from "./tarife.mjs";

const ucretliAgirlik = (g) => Math.max(g.agirlikGram, g.hacimCm3 / 3);

export function ucret(gonderi, bolge) {
  const a = ucretliAgirlik(gonderi);
  const kademe = TARIFE.kademeler.find(([ust]) => a <= ust);
  const [sonUst, sonUcret] = TARIFE.kademeler[TARIFE.kademeler.length - 1];
  const taban = kademe !== undefined
    ? kademe[1]
    : sonUcret + Math.ceil((a - sonUst) / 1000) * TARIFE.kiloBasiEk;
  return Math.max(Math.round(taban * TARIFE.bolgeKatsayisi[bolge]), TARIFE.asgariUcret);
}

export const toplam = (gonderiler, bolge) =>
  gonderiler.reduce((t, g) => t + ucret(g, bolge), 0);
```

İkincisi bir gönderinin bölgelere göre en düşük ve en yüksek ücretini verir; bölge
listesini katsayı tablosunun anahtarlarından çıkarır.

```js
// acik/teklif.mjs — istemci bolge listesini katsayi tablosunun anahtarlarindan cikarir
import { TARIFE } from "./tarife.mjs";
import { ucret } from "./siparis.mjs";

export function aralik(gonderi) {
  const ucretler = Object.keys(TARIFE.bolgeKatsayisi).map((b) => ucret(gonderi, Number(b)));
  return { enAz: Math.min(...ucretler), enCok: Math.max(...ucretler) };
}
```

Üçüncüsü en üst kademeyi aşan ağırlığın ek yükünü toplar.

```js
// acik/kapasite.mjs — istemci en ust kademe sinirini ve kilo basi eki dogrudan okur
import { TARIFE } from "./tarife.mjs";

export function asimEki(gonderiler) {
  const [sonUst] = TARIFE.kademeler[TARIFE.kademeler.length - 1];
  return gonderiler.reduce((t, g) => {
    const a = Math.max(g.agirlikGram, g.hacimCm3 / 3);
    return t + (a <= sonUst ? 0 : Math.ceil((a - sonUst) / 1000) * TARIFE.kiloBasiEk);
  }, 0);
}
```

İkinci sunumda temsil modülün içinde kalır; dışarıya üç ad açılır. Açılan adlar veri değil,
istemcilerin gerçekten sorduğu sorulardır.

```js
// kapali/tarife.mjs — temsil ozel; disariya uc ad acilir
const KADEMELER = [[1000, 4500], [5000, 7000], [10000, 11000]];
const KILO_BASI_EK = 1800;
const BOLGE_KATSAYISI = { 1: 1, 2: 1.25, 3: 1.6 };
const ASGARI_UCRET = 5000;

export function asimEki(agirlik) {
  const sonUst = KADEMELER.at(-1)[0];
  return agirlik <= sonUst ? 0 : Math.ceil((agirlik - sonUst) / 1000) * KILO_BASI_EK;
}

export function ucret(agirlik, bolge) {
  const kademe = KADEMELER.find(([ust]) => agirlik <= ust);
  const taban = kademe !== undefined ? kademe[1] : KADEMELER.at(-1)[1] + asimEki(agirlik);
  return Math.max(Math.round(taban * BOLGE_KATSAYISI[bolge]), ASGARI_UCRET);
}

export const bolgeler = () => Object.keys(BOLGE_KATSAYISI).map(Number);
```

Aynı üç istemci bu sunumda yalnız çağrı yapar.

```js
// kapali/siparis.mjs — istemci yalnizca ucret adini kullanir, temsili bilmez
import { ucret as tarifeUcreti } from "./tarife.mjs";

const ucretliAgirlik = (g) => Math.max(g.agirlikGram, g.hacimCm3 / 3);

export const ucret = (gonderi, bolge) => tarifeUcreti(ucretliAgirlik(gonderi), bolge);

export const toplam = (gonderiler, bolge) =>
  gonderiler.reduce((t, g) => t + ucret(g, bolge), 0);
```

```js
// kapali/teklif.mjs — bolge listesi tarifeye sorulur
import { bolgeler } from "./tarife.mjs";
import { ucret } from "./siparis.mjs";

export function aralik(gonderi) {
  const ucretler = bolgeler().map((b) => ucret(gonderi, b));
  return { enAz: Math.min(...ucretler), enCok: Math.max(...ucretler) };
}
```

```js
// kapali/kapasite.mjs — asim eki tarifeye sorulur
import { asimEki as tarifeAsimi } from "./tarife.mjs";

export function asimEki(gonderiler) {
  return gonderiler.reduce(
    (t, g) => t + tarifeAsimi(Math.max(g.agirlikGram, g.hacimCm3 / 3)), 0);
}
```

#### İki Sayı: Açılan Ad ve Bilinen Ayrıntı

Ölçüm, tarifeye ait yedi ayrıntıyı önceden adlandırır ve her istemci kaynağında hangisinin
göründüğünü arar. Yedi ayrıntı listenin kendisinde yazılıdır; sayılan şey budur.

```js
// olcum.mjs — disa acilan ad sayisini ve istemcilerin bildigi temsil ayrintisini sayar
import { readFileSync } from "node:fs";

const AYRINTI = [
  ["kademe listesi", /\.kademeler\b/],
  ["kademenin cift olusu", /\(\[ust\]\)|\[sonUst(, sonUcret)?\]|kademe\[1\]/],
  ["kademelerin siralanisi", /kademeler\[TARIFE\.kademeler\.length - 1\]/],
  ["kilo basi ek adi", /\.kiloBasiEk\b/],
  ["katsayi tablosu", /\.bolgeKatsayisi\b/],
  ["bolgelerin anahtar olusu", /Object\.keys\(TARIFE\./],
  ["asgari ucret adi", /\.asgariUcret\b/],
];
const ISTEMCILER = ["siparis.mjs", "teklif.mjs", "kapasite.mjs"];

for (const dizin of ["acik", "kapali"]) {
  const tarife = readFileSync(`${dizin}/tarife.mjs`, "utf8");
  console.log(`${dizin.padEnd(6)} disa acilan ad = ${(tarife.match(/^export /gm) ?? []).length}`);
  const tumu = new Set();
  for (const dosya of ISTEMCILER) {
    const kaynak = readFileSync(`${dizin}/${dosya}`, "utf8");
    const bilinen = AYRINTI.filter(([, k]) => k.test(kaynak)).map(([ad]) => ad);
    bilinen.forEach((a) => tumu.add(a));
    console.log(`  ${dosya.padEnd(13)} bildigi ayrinti = ${bilinen.length}  ${bilinen.join(", ")}`);
  }
  console.log(`  ayri ayrinti toplami = ${tumu.size}`);
}
```

```sh
node olcum.mjs
```

```
acik   disa acilan ad = 1
  siparis.mjs   bildigi ayrinti = 6  kademe listesi, kademenin cift olusu, kademelerin siralanisi, kilo basi ek adi, katsayi tablosu, asgari ucret adi
  teklif.mjs    bildigi ayrinti = 2  katsayi tablosu, bolgelerin anahtar olusu
  kapasite.mjs  bildigi ayrinti = 4  kademe listesi, kademenin cift olusu, kademelerin siralanisi, kilo basi ek adi
  ayri ayrinti toplami = 7
kapali disa acilan ad = 3
  siparis.mjs   bildigi ayrinti = 0
  teklif.mjs    bildigi ayrinti = 0
  kapasite.mjs  bildigi ayrinti = 0
  ayri ayrinti toplami = 0
```

İki sayı ters yönde. Açık sunum **bir** ad açıyor, kapalı sunum **üç**. Ad sayısına
bakılırsa açık sunum daha dar bir yüzeye sahip görünür. Ayrıntı sayısına bakıldığında
tablo tersine döner: açık sunumun istemcileri yedi ayrı temsil ayrıntısını biliyor, kapalı
sunumunkiler hiçbirini. Dışa açılan ad sayısı bir soyutlama ölçütü değildir; ölçüt,
adların **arkasında ne kaldığıdır**.

Karşılaştırmanın anlamlı olması için iki sunumun aynı sonucu ürettiğinin gösterilmesi
gerekir. Aşağıdaki denetleyici üç istemciyi iki dizinde de çalıştırır ve bilinen değerlerle
karşılaştırır.

```js
// kontrol.mjs — uc istemciyi iki dizinde de calistirip bilinen degerlerle karsilastirir
const GONDERILER = [
  { agirlikGram: 800, hacimCm3: 900 },
  { agirlikGram: 4000, hacimCm3: 30000 },
  { agirlikGram: 12000, hacimCm3: 900 },
];
const BEKLENEN = { "siparis.mjs": 37625, "teklif.mjs": "5000/7200", "kapasite.mjs": 3600 };

async function dene(dizin, dosya) {
  if (dosya === "siparis.mjs") {
    const { toplam } = await import(`./${dizin}/siparis.mjs`);
    return toplam(GONDERILER, 2);
  }
  if (dosya === "teklif.mjs") {
    const { aralik } = await import(`./${dizin}/teklif.mjs`);
    const a = aralik(GONDERILER[0]);
    return `${a.enAz}/${a.enCok}`;
  }
  const { asimEki } = await import(`./${dizin}/kapasite.mjs`);
  return asimEki(GONDERILER);
}

for (const dizin of ["acik", "kapali"]) {
  const kirik = [];
  for (const dosya of Object.keys(BEKLENEN)) {
    let sonuc;
    try { sonuc = await dene(dizin, dosya); } catch (e) { sonuc = e.constructor.name; }
    if (sonuc !== BEKLENEN[dosya]) kirik.push(`${dosya}->${sonuc}`);
  }
  console.log(`${dizin.padEnd(6)} kirilan istemci = ${kirik.length}  ${kirik.join(" ")}`);
}
```

```sh
node kontrol.mjs
```

```
acik   kirilan istemci = 0
kapali kirilan istemci = 0
```

Aynı sonuçlar üretiliyorsa, gizlemenin kod miktarına bedeli de sorulabilir. Sanılanın
aksine bu örnekte bedel negatiftir.

```sh
for d in acik kapali; do
  echo "$d  bos olmayan satir = $(cat $d/tarife.mjs $d/siparis.mjs $d/teklif.mjs $d/kapasite.mjs | grep -c .)"
done
```

```
acik  bos olmayan satir = 37
kapali  bos olmayan satir = 34
```

Kapalı sunum üç satır daha kısa. Modüle eklenen üç işlev gövdesi, istemcilerden kalkan
tekrar eden temsil bilgisinden daha az yer tuttu. Kod miktarı, ayrıntı sayısı yeterince çok
istemciye yayıldığında soyutlama lehine döner.

#### Ayrıntı Değiştiğinde

Yedi ayrıntının ikisi değişir: kademeler adlandırılmış alanlara geçer ve bölge katsayıları
bir listeye dönüşür. Tarifenin **anlamı** aynı kalır — aynı kademeler, aynı katsayılar.

```js
// acik/tarife-v2.mjs — ayni tarife: kademeler adli alanlarla, bolgeler liste olarak
export const TARIFE = {
  kademeler: [
    { ustGram: 1000, ucret: 4500 },
    { ustGram: 5000, ucret: 7000 },
    { ustGram: 10000, ucret: 11000 },
  ],
  kiloBasiEk: 1800,
  bolgeKatsayisi: [{ bolge: 1, katsayi: 1 }, { bolge: 2, katsayi: 1.25 }, { bolge: 3, katsayi: 1.6 }],
  asgariUcret: 5000,
};
```

```js
// kapali/tarife-v2.mjs — ayni temsil degisikligi; disa acilan uc ad aynen korunur
const KADEMELER = [
  { ustGram: 1000, ucret: 4500 },
  { ustGram: 5000, ucret: 7000 },
  { ustGram: 10000, ucret: 11000 },
];
const KILO_BASI_EK = 1800;
const BOLGE_KATSAYISI = [{ bolge: 1, katsayi: 1 }, { bolge: 2, katsayi: 1.25 }, { bolge: 3, katsayi: 1.6 }];
const ASGARI_UCRET = 5000;

export function asimEki(agirlik) {
  const sonUst = KADEMELER.at(-1).ustGram;
  return agirlik <= sonUst ? 0 : Math.ceil((agirlik - sonUst) / 1000) * KILO_BASI_EK;
}

export function ucret(agirlik, bolge) {
  const kademe = KADEMELER.find((k) => agirlik <= k.ustGram);
  const taban = kademe !== undefined ? kademe.ucret : KADEMELER.at(-1).ucret + asimEki(agirlik);
  return Math.max(Math.round(taban * BOLGE_KATSAYISI.find((b) => b.bolge === bolge).katsayi), ASGARI_UCRET);
}

export const bolgeler = () => BOLGE_KATSAYISI.map((b) => b.bolge);
```

Her iki dizinde de değiştirilen dosya sayısı birdir. Aynı denetleyici yeniden çalıştırılır.

```sh
cp acik/tarife-v2.mjs acik/tarife.mjs
cp kapali/tarife-v2.mjs kapali/tarife.mjs
node kontrol.mjs
```

```
acik   kirilan istemci = 3  siparis.mjs->TypeError teklif.mjs->TypeError kapasite.mjs->TypeError
kapali kirilan istemci = 0
```

Tek dosyada yapılan bir değişiklik açık sunumda üç istemcinin üçünü de kırdı, kapalı
sunumda hiçbirini. Kırılma sayısı, ölçülen yedi ayrıntının doğrudan sonucudur: bir
istemci bir ayrıntıyı biliyorsa, o ayrıntı değiştiğinde kırılır.

Buradaki kırılmaların hepsi çalışma zamanı hatası verdi; sessiz kalmadı. Bu, temsil
değişikliğinin tipini bozacak kadar büyük olmasındandır. Daha küçük değişiklikler —
kademelerin sıralamasının bozulması, birim değişimi — aynı kaynaklarda hata değil **yanlış
sayı** üretir; kırılan istemci sayısı yine üç olur, ama sayaç yerine bir doğrulama
gerektirir.

#### Gizlemenin Bedeli

Soyutlama bedava değildir. Kod miktarı bedel değildi — ölçüm 37'ye karşı 34 satır verdi.
Gerçek bedel, doğrudan ölçülen üç sayısıdır: kapalı modül **üç** soru
yanıtlar. Dördüncü bir soru — tarife tablosunun belge olarak yazdırılması, kademe
sınırlarının bir arayüzde listelenmesi — açık sunumda modüle hiç dokunmadan yanıtlanır,
kapalı sunumda modülün değiştirilmesini gerektirir. Gizleme, yeni ihtiyacın maliyetini
istemciden modül sahibine taşır.

Bu bedel, soyutlamanın yanlış çizildiği durumda ödenip karşılığı alınmaz. Modül kademe
listesini olduğu gibi döndüren bir dördüncü ad açarsa, ölçülen sıfır yeniden yediye doğru
tırmanır: gizlenen temsil, adı değişmiş biçimde yeniden dışarı çıkar. Bu duruma **sızdıran
soyutlama** (leaky abstraction) denir. Dördüncü sorunun doğru yanıtı, iç listeyi vermek
değil, sorulan soruyu modülün kendi sözcükleriyle karşılayan bir görünüm döndürmektir —
alanları modülün tanımladığı, iç temsille bağı olmayan bir özet.

#### Özet

- Dışa açılan ad sayısı tek başına soyutlama ölçütü değildir: açık sunum bir ad açtı, kapalı
  sunum üç; buna rağmen ayrıntı bakımından ilişki tersineydi.
- İstemcilerin bildiği temsil ayrıntısı açık sunumda yedi, kapalı sunumda sıfır ölçüldü;
  bu sayı, değişimde kırılacak istemci sayısını önceden verir.
- Tek dosyada yapılan bir temsil değişikliği açık sunumda üç istemcinin üçünü de kırdı,
  kapalı sunumda hiçbirini kırmadı.
- Soyutlamanın kod bedeli bu örnekte negatif çıktı (37'ye karşı 34 satır); ayrıntı yeterince
  çok istemciye yayıldığında gizleme kodu kısaltır.
- Gerçek bedel modülün yanıtladığı soru sayısıyla ölçülür: kapalı modül üç soru yanıtlar,
  dördüncüsü modülün değiştirilmesini gerektirir.
- İç temsili adı değişmiş biçimde geri veren bir ad, sızdıran soyutlamadır; ayrıntı sayısını
  sıfırdan geri tırmandırır.

#### Sonraki Adım

Kapalı tarife modülü tek bir tarife biçimini gizliyor. Gerçek bir ücretlendirme
kitaplığında tarife türü birden fazladır: yurt içi kademe tarifesi, aktarmalı tarife,
sözleşmeli müşteri tarifesi. Aynı adları farklı gerçekleştirimlerle karşılamanın
yollarından biri, bir türü bir başkasından türetmektir. Sonraki ders bu ilişkiyi ele alır
ve türetmenin ne zaman gerçek bir alt tip ürettiğini ölçer: aynı üst tip üzerinden çalışan
bir istemci, sözleşmeye uyan ve uymayan iki alt sınıfla koşturulur ve uymayan sürümün kaç
yanlış sonuç ürettiği sayılır.

### Kalıtım

Source: https://academia.sh/tr/kurslar/paradigmalar/kalitim

Kalıtımın iki ayrı vaadinin ayrılması: gerçekleştirim paylaşımı satır sayısıyla, alt tip ilişkisi ise üç maddelik bir ücret sözleşmesiyle ölçülür; sözleşmeye uyan ve uymayan iki alt sınıf aynı istemciyle koşturulup uymayan sürümün ürettiği yanlış sonuç sayılır.

Önceki ders tarife modülünün temsilini gizledi ve istemcilerin bildiği ayrıntıyı sıfıra
indirdi. O modül tek bir tarife biçimini gizliyordu. Gerçek bir ücretlendirme kitaplığında
tarife türü birden fazladır: sigortalı gönderi tarifesi, elden teslim tarifesi, sözleşmeli
müşteri tarifesi. Aynı adları farklı gerçekleştirimlerle karşılamanın yollarından biri bir
türü bir başkasından **türetmektir**.

**Kalıtım** (inheritance), bir sınıfın bir başkasının alanlarını ve yöntemlerini devralması
ve gerektiğinde bunların bir bölümünü kendi gerçekleştirimiyle değiştirmesidir. Programlama
Temelleri kursunda tanıtılan tanım budur. Bu derste sorulan soru şudur: türetme her zaman
bir **alt tip** (subtype) üretir mi, üretmediğinde bunu ne gösterir ve bedeli nedir.

#### Kalıtımın İki Ayrı Vaadi

Kalıtım tek bir araçmış gibi anılır, ama iki ayrı şey vaat eder ve bu ikisi birbirini
gerektirmez.

Birincisi **gerçekleştirim paylaşımı**: alt sınıf, üst sınıfın kodunu tekrar yazmaz. Bu vaat
sayılabilir ve neredeyse her zaman tutulur.

İkincisi **alt tip ilişkisi**: üst tipi bekleyen her yere alt tip konabilir ve program doğru
çalışmaya devam eder. Bu vaat kodun biçimiyle değil, davranışıyla ilgilidir ve dil onu
denetlemez. `extends` yazmak birinciyi kesin olarak sağlar; ikinciyi hiç sağlamayabilir.

İkisini ayırmak için üst tipin ne söz verdiği yazılı olmalıdır. Bir üst tipin **sözleşmesi**,
yöntemlerinin girdileri ve sonuçları hakkında verdiği güvencelerdir. Alt tip bu güvenceleri
zayıflatamaz: girdi kümesini daraltamaz, sonuç hakkında verilen sözü bozamaz, üst tipin
değişmezini çiğneyemez. Bu kural Liskov yerine koyma ilkesi adıyla bilinir.

#### Üst Tip ve Ücret Sözleşmesi

Kademeli tarife bir sınıfa dönüşür. Kademeler, katsayılar ve eşikler özeldir; dışarıya
`ucret`, `bolgeler` ve `asgari` açılır. `taban` yöntemi alt sınıfların kullanımına açıktır.

```js
// tarife.mjs — ust tip: kademeli tarife; ucret sozlesmesini bu sinif tanimlar
export class Tarife {
  #kademeler; #kiloBasiEk; #katsayi; #asgari;

  constructor({ kademeler, kiloBasiEk, katsayi, asgari }) {
    this.#kademeler = kademeler;
    this.#kiloBasiEk = kiloBasiEk;
    this.#katsayi = katsayi;
    this.#asgari = asgari;
  }

  get asgari() { return this.#asgari; }

  bolgeler() { return Object.keys(this.#katsayi).map(Number); }

  taban(agirlik) {
    const kademe = this.#kademeler.find(([ust]) => agirlik <= ust);
    if (kademe !== undefined) return kademe[1];
    const [sonUst, sonUcret] = this.#kademeler.at(-1);
    return sonUcret + Math.ceil((agirlik - sonUst) / 1000) * this.#kiloBasiEk;
  }

  ucret(agirlik, bolge) {
    return Math.max(Math.round(this.taban(agirlik) * this.#katsayi[bolge]), this.#asgari);
  }
}

export const AYAR = {
  kademeler: [[1000, 4500], [5000, 7000], [10000, 11000]],
  kiloBasiEk: 1800,
  katsayi: { 1: 1, 2: 1.25, 3: 1.6 },
  asgari: 5000,
};
```

`ucret` yönteminin sözleşmesi üç maddedir:

1. **S1** — Her pozitif ağırlık ve `bolgeler()` listesindeki her bölge için tanımlıdır;
   sonlu bir sayı döndürür.
2. **S2** — Sonuç asgari ücretin altına inmez.
3. **S3** — Sonuç ağırlıkla azalmaz: `a1 <= a2` ise `ucret(a1) <= ucret(a2)`.

Üçüncü madde koddan okunamaz; kademelerin artan olması ve katsayının pozitifliğinden
çıkar. Yazılı olmadığı sürece alt sınıf yazan kişinin bunu bilmesi için bir neden yoktur.
Sözleşmeyi yazmak, kalıtımın ikinci vaadini denetlenebilir kılmanın tek yoludur.

#### Sözleşmeye Uyan ve Uymayan İki Alt Sınıf

İlk alt sınıf sigortalı gönderi tarifesidir: ağırlıkla artan bir sigorta payını üst sınıfın
sonucuna ekler.

```js
// sigortali.mjs — sozlesmeye uyan alt sinif: agirlikla artan bir sigorta payi ekler
import { Tarife } from "./tarife.mjs";

export class SigortaliTarife extends Tarife {
  #gramBasi;
  constructor(ayar, gramBasi) { super(ayar); this.#gramBasi = gramBasi; }
  ucret(agirlik, bolge) {
    return super.ucret(agirlik, bolge) + Math.round(agirlik * this.#gramBasi);
  }
}
```

İkincisi elden teslim tarifesidir. Talep gerçektir: 6 ile 9 kilogram arasındaki gönderiler
tek kişiyle taşınamadığı için ek ücretlendirilir. Gerçekleştirim de doğrudan görünür.

```js
// elden-teslim.mjs — sozlesmeyi ciyneyen alt sinif: bir agirlik bandina ek ucret koyar
import { Tarife } from "./tarife.mjs";

export class EldenTeslimTarifesi extends Tarife {
  #bandaEk;
  constructor(ayar, bandaEk) { super(ayar); this.#bandaEk = bandaEk; }
  ucret(agirlik, bolge) {
    const taban = super.ucret(agirlik, bolge);
    return agirlik >= 6000 && agirlik <= 9000 ? taban + this.#bandaEk : taban;
  }
}
```

İki sınıf da aynı biçimde yazılmıştır: aynı üst sınıf, aynı yöntem imzası, aynı dönüş tipi.
Aralarındaki fark bandın **çıkışındadır**: 9000 gramda ücret ek yükle birlikte yüksektir,
9001 gramda ek kalkar ve ücret düşer. S3 maddesi çiğnenmiştir.

Gerçekleştirim paylaşımı vaadi ikisinde de tutuldu. Ölçüsü satır sayısıdır.

```sh
for f in tarife.mjs sigortali.mjs elden-teslim.mjs; do echo "$f  $(grep -c . $f) satir"; done
```

```
tarife.mjs  27 satir
sigortali.mjs  9 satir
elden-teslim.mjs  10 satir
```

Yirmi yedi satırlık kademe, katsayı ve asgari ücret mantığı iki alt sınıfın hiçbirinde
tekrar edilmedi; ikisi de dokuz–on satırla yetindi. Kalıtımın birinci vaadi bakımından iki
alt sınıf arasında hiçbir fark yok. Fark yalnız ikinci vaatte.

#### Sözleşme Denetleyicisi

Üç madde bir ağırlık ızgarası üzerinde denenir. Denetleyici üst tipe bağımlıdır; hangi alt
sınıfın verildiğini bilmez.

```js
// sozlesme.mjs — ucret sozlesmesinin uc maddesini bir agirlik izgarasinda dener
export function ihlaller(tarife) {
  const kirik = new Set();
  for (const bolge of tarife.bolgeler()) {
    let onceki = -Infinity;
    for (let a = 100; a <= 20000; a += 100) {
      let u;
      try { u = tarife.ucret(a, bolge); } catch { kirik.add("S1 her gecerli girdide tanimli"); continue; }
      if (!Number.isFinite(u)) kirik.add("S1 her gecerli girdide tanimli");
      if (u < tarife.asgari) kirik.add("S2 asgari ucretin altina inmez");
      if (u < onceki) kirik.add("S3 agirlikla azalmaz");
      onceki = u;
    }
  }
  return [...kirik].sort();
}
```

#### Üst Tip Üzerinden Çalışan İstemci

İstemci, verilen bütçeyle gönderilebilecek en ağır gönderiyi bulur. İkili arama kullanır ve
bu seçim doğrudan S3 maddesine dayanır: bir ağırlıkta ücret bütçeyi aşıyorsa, daha ağır
hiçbir gönderi bütçeye girmez. Aynı dosya, doğruluk ölçütü olarak tek tek tarayan yavaş bir
sürümü de içerir.

```js
// butce.mjs — istemci: verilen butceyle gonderilebilecek en agir gonderiyi bulur
const UST = 16000;

export function ikiliArama(tarife, bolge, butce) {
  let alt = 0, ust = UST;
  while (alt < ust) {
    const orta = Math.ceil((alt + ust) / 2);
    if (tarife.ucret(orta, bolge) <= butce) alt = orta; else ust = orta - 1;
  }
  return alt;
}

export function tarama(tarife, bolge, butce) {
  let en = 0;
  for (let a = 0; a <= UST; a += 1) if (tarife.ucret(a, bolge) <= butce) en = a;
  return en;
}
```

Koşum, iki alt sınıfı önce sözleşme denetiminden, sonra üç bölge ve altmış bütçe üzerinde
istemciden geçirir.

```js
// kosum.mjs — iki alt sinifi ayni sozlesme denetiminden ve ayni istemciden gecirir
import { AYAR } from "./tarife.mjs";
import { SigortaliTarife } from "./sigortali.mjs";
import { EldenTeslimTarifesi } from "./elden-teslim.mjs";
import { ihlaller } from "./sozlesme.mjs";
import { ikiliArama, tarama } from "./butce.mjs";

const BUTCELER = Array.from({ length: 60 }, (_, i) => 5000 + i * 500);

for (const [ad, tarife] of [
  ["SigortaliTarife", new SigortaliTarife(AYAR, 0.2)],
  ["EldenTeslimTarifesi", new EldenTeslimTarifesi(AYAR, 8000)],
]) {
  const kirik = ihlaller(tarife);
  let yanlis = 0;
  let enBuyukFark = 0;
  for (const bolge of tarife.bolgeler()) {
    for (const butce of BUTCELER) {
      const bulunan = ikiliArama(tarife, bolge, butce);
      const dogru = tarama(tarife, bolge, butce);
      if (bulunan !== dogru) { yanlis += 1; enBuyukFark = Math.max(enBuyukFark, dogru - bulunan); }
    }
  }
  console.log(`${ad}`);
  console.log(`  sozlesme ihlali    = ${kirik.length}  ${kirik.join("; ")}`);
  console.log(`  yanlis sonuc       = ${yanlis}/${BUTCELER.length * 3}`);
  console.log(`  en buyuk sapma     = ${enBuyukFark} gram`);
}
```

```sh
node kosum.mjs
```

```
SigortaliTarife
  sozlesme ihlali    = 0
  yanlis sonuc       = 0/180
  en buyuk sapma     = 0 gram
EldenTeslimTarifesi
  sozlesme ihlali    = 1  S3 agirlikla azalmaz
  yanlis sonuc       = 48/180
  en buyuk sapma     = 8001 gram
```

Elden teslim tarifesiyle 180 sorgunun 48'i yanlış yanıtlandı. En büyük sapmada istemci,
bütçenin gerçekte 8001 gram daha ağır bir gönderiyi karşıladığı durumda daha hafif bir
gönderi önerdi. İstemcinin kodunda hata yok; ikili arama S3 maddesi geçerliyken doğrudur.
Hata, sözleşmeyi çiğneyen alt sınıfın üst tip yerine konmasıyla ortaya çıktı.

Sayının 180'in tamamı olmaması da bilgi taşıyor. İhlal, ancak aramanın bir yoklaması ek
ücret bandına düştüğünde görünür oluyor; diğer sorgularda program doğru yanıt veriyor.
Sözleşme ihlalleri her çağrıda değil, belirli girdi bölgelerinde patlar; bu yüzden örnek
girdilerle yapılan sınamalarda gözden kaçar.

#### Sözleşme Nerede Yazılı

Elden teslim tarifesi hiçbir tip denetiminden geçmez değildir; tam tersine her denetimden
geçer. Yöntem imzası aynı, dönüş tipi aynı, `extends` bağı geçerli. Statik tipleme S1'in bir
bölümünü — parametre ve dönüş tiplerini — yakalar; S2 ve S3'ü yakalamaz, çünkü bunlar
tiplerin değil değerlerin ilişkisidir.

Ölçülen 48 yanlış sonuç, sözleşmenin yazılı olmamasının bedelidir. Üç maddenin metne
dökülmesi ve `sozlesme.mjs` gibi bir denetleyiciyle her alt sınıfa uygulanması, bu bedeli
istemcinin çalışma zamanından alt sınıfın yazıldığı ana taşır. Aynı denetleyici ihlali
tek satırda gösterdi.

Elden teslim ihtiyacının kendisi yanlış değildi; onu bir alt sınıf olarak modellemek
yanlıştı. Bant ücreti tarifenin bir alt türü değil, tarifeye eklenen ayrı bir hizmet
kalemidir; ücretin yanında ayrı bir alan olarak taşınırsa S3 çiğnenmez.

#### Özet

- Kalıtım iki ayrı şey vaat eder: gerçekleştirim paylaşımı ve alt tip ilişkisi; `extends`
  birincisini kesin olarak sağlar, ikincisini hiç sağlamayabilir.
- Gerçekleştirim paylaşımı iki alt sınıfta da tutuldu: 27 satırlık üst sınıf mantığı dokuz
  ve on satırlık alt sınıflarda tekrar edilmedi.
- Alt tip ilişkisi ancak üst tipin sözleşmesi yazılıysa denetlenebilir; buradaki sözleşme üç
  maddeydi ve üçüncüsü koddan okunamıyordu.
- Sözleşmeye uyan alt sınıf 0 ihlal ve 180 sorguda 0 yanlış sonuç verdi; uymayan alt sınıf
  1 ihlal ve 48 yanlış sonuç verdi, en büyük sapma 8001 gramdı.
- İhlal her çağrıda değil belirli girdi bölgelerinde görünür; örnek girdilerle yapılan
  sınamada 180 sorgunun 132'si doğru yanıt verdi.
- Statik tipleme yalnız imza uyumunu denetler; sonucun değerine ilişkin maddeler yazılı bir
  sözleşme ve onu uygulayan bir denetleyici gerektirir.

#### Sonraki Adım

Bu derste iki alt sınıf da aynı adla — `ucret` — farklı davrandı ve istemci hangisiyle
çalıştığını bilmedi. Bu, tek bir adın birden çok davranışa karşılık gelmesidir ve alt tip
ilişkisi bunun yalnız bir yoludur. Sonraki ders üç yolu da kurar: alt tip, parametrik ve
geçici çok biçimlilik. Ölçüm de değişir — aynı problem üç yolla gerçekleştirilir, sonra
sisteme yeni bir tarife türü ve yeni bir işlem eklenir ve her yolda dokunulan dosya sayısı
sayılır.

### Çok Biçimlilik

Source: https://academia.sh/tr/kurslar/paradigmalar/cok-bicimlilik

Alt tip, parametrik ve geçici çok biçimliliğin aynı tarife problemi üzerinde ayrı ayrı gerçekleştirilmesi: sisteme yeni bir tarife türü ve yeni bir işlem eklendiğinde her yolda dokunulan dosya sayısının sayılması ve iki genişleme yönünün karşıtlığının gösterilmesi.

Önceki derste iki alt sınıf aynı adı — `ucret` — farklı gerçekleştirdi ve istemci hangisiyle
çalıştığını bilmedi. Bu, tek bir adın birden çok davranışa karşılık gelmesidir. Alt tip
ilişkisi bunun yollarından yalnız biridir ve seçilen yol, sistemin hangi yönde
genişleyebileceğini belirler.

**Çok biçimlilik** (polymorphism), tek bir arayüzün birden çok tiple çalışabilmesidir.
Programlama Temelleri kursunda tanıtılan tanım budur. Bu derste üç ayrı biçimi aynı problem
üzerinde kurulur ve aralarındaki seçimin bedeli sayılır.

#### Üç Ayrı Biçim

**Alt tip çok biçimliliği** (subtype polymorphism), çağrının nesnenin çalışma zamanındaki
tipine göre yönlendirilmesidir. Ad ortak bir üst tipte tanımlıdır, gerçekleştirim alt
tiplerdedir.

**Parametrik çok biçimlilik** (parametric polymorphism), aynı kodun tipten **bağımsız**
çalışmasıdır. Kod hiçbir tipe bakmaz; yaptığı iş tüm tipler için aynıdır. Tipe özgü olan
kısım dışarıdan verilir.

**Geçici çok biçimlilik** (ad hoc polymorphism), her tip için ayrı bir gerçekleştirimin
yazılıp ortak bir adla çağrılmasıdır. Aşırı yükleme destekleyen dillerde bu, aynı adın
farklı imzalarla tanımlanmasıdır; burada tür etiketine bakan bir yönlendirme tablosuyla
gerçekleştirilir.

Problem üçünde de aynıdır: iki tarife türü (`kademeli`, `sabit`) ve iki işlem (`ucret`,
`teslimGunu`).

#### Aynı Problem, Üç Gerçekleştirim

Alt tip yolunda üst tip işlemleri bildirir, her tür kendi dosyasında gerçekleştirir.

```js
// v1/alttip/tarife.mjs — ust tip: her tarife turu bu iki yontemi gerceklestirir
export class Tarife {
  ucret(gonderi) { throw new Error(`ucret gerceklestirilmedi: ${gonderi}`); }
  teslimGunu(gonderi) { throw new Error(`teslimGunu gerceklestirilmedi: ${gonderi}`); }
}
```

```js
// v1/alttip/kademeli.mjs — kademeli tarife turu
import { Tarife } from "./tarife.mjs";

export class Kademeli extends Tarife {
  ucret(g) {
    return g.agirlikGram <= 1000 ? 4500 : 4500 + Math.ceil((g.agirlikGram - 1000) / 1000) * 1800;
  }
  teslimGunu(g) { return g.bolge === 1 ? 1 : 3; }
}
```

```js
// v1/alttip/sabit.mjs — sabit ucretli tarife turu
import { Tarife } from "./tarife.mjs";

export class Sabit extends Tarife {
  ucret() { return 9000; }
  teslimGunu() { return 2; }
}
```

Geçici yolda tür bir etikettir ve her **işlem** kendi dosyasında tüm türleri tek tabloda
toplar. Bölünme yönü alt tip yolunun tam tersidir.

```js
// v1/gecici/ucret.mjs — ucret islemi: tum turler icin tek tabloda
const TABLO = {
  kademeli: (g) => (g.agirlikGram <= 1000
    ? 4500 : 4500 + Math.ceil((g.agirlikGram - 1000) / 1000) * 1800),
  sabit: () => 9000,
};

export const ucret = (tarife, gonderi) => TABLO[tarife.tur](gonderi);
```

```js
// v1/gecici/teslim-gunu.mjs — teslim gunu islemi: tum turler icin tek tabloda
const TABLO = { kademeli: (g) => (g.bolge === 1 ? 1 : 3), sabit: () => 2 };

export const teslimGunu = (tarife, gonderi) => TABLO[tarife.tur](gonderi);
```

Parametrik yolda tür hiç geçmez. Aşağıdaki iki işlev, ölçütü dışarıdan aldığı için her
tarife temsiliyle çalışır: sınıf örnekleriyle de, etiketli düz nesnelerle de.

```js
// v1/parametrik/secim.mjs — tur bilgisi kullanmaz; olcutu disaridan alir
export function enUcuz(secenekler, olcut) {
  let en = secenekler[0];
  for (const s of secenekler) if (olcut(s) < olcut(en)) en = s;
  return en;
}

export const siraya = (secenekler, olcut) =>
  [...secenekler].sort((a, b) => olcut(a) - olcut(b));
```

Üç yol aynı gönderi üzerinde aynı sonuçları verir.

```js
// v1/kosum.mjs — uc yolun ayni gonderide ayni sonuclari verdigini gosterir
import { Kademeli } from "./alttip/kademeli.mjs";
import { Sabit } from "./alttip/sabit.mjs";
import { ucret } from "./gecici/ucret.mjs";
import { teslimGunu } from "./gecici/teslim-gunu.mjs";
import { enUcuz } from "./parametrik/secim.mjs";

const G = { agirlikGram: 4000, bolge: 2 };
const nesneler = [new Kademeli(), new Sabit()];
const kayitlar = [{ tur: "kademeli" }, { tur: "sabit" }];

console.log("alttip     ", nesneler.map((t) => `${t.ucret(G)}/${t.teslimGunu(G)}`).join(" "));
console.log("gecici     ", kayitlar.map((t) => `${ucret(t, G)}/${teslimGunu(t, G)}`).join(" "));
console.log("parametrik ", enUcuz(nesneler, (t) => t.ucret(G)).constructor.name,
  enUcuz(kayitlar, (t) => ucret(t, G)).tur);
```

```sh
node v1/kosum.mjs
```

```
alttip      9900/3 9000/2
gecici      9900/3 9000/2
parametrik  Sabit sabit
```

Parametrik satır, o yolun karakterini gösteriyor: `enUcuz` hem sınıf örnekleri hem etiketli
kayıtlar üzerinde çalıştı, çünkü ikisine de bakmadı — yalnız kendisine verilen ölçütü
çağırdı.

#### İki Genişleme

Sisteme iki değişiklik gelir. Birincisi yeni bir **tür**: aktarma noktasından geçen gönderi
tarifesi. Aşağıdaki blok ikinci sürümü birinciden kopyalayarak kurar ve bu türü ekler.

```sh
cp -r v1 v2
cat > v2/alttip/aktarmali.mjs <<'DOSYA'
// v2/alttip/aktarmali.mjs — yeni tur: aktarma noktasindan gecen gonderi
import { Tarife } from "./tarife.mjs";

export class Aktarmali extends Tarife {
  ucret(g) { return 6000 + Math.ceil(g.agirlikGram / 1000) * 1200; }
  teslimGunu() { return 5; }
}
DOSYA
cat > v2/gecici/ucret.mjs <<'DOSYA'
// v2/gecici/ucret.mjs — ucret islemi: yeni tur icin satir eklendi
const TABLO = {
  kademeli: (g) => (g.agirlikGram <= 1000
    ? 4500 : 4500 + Math.ceil((g.agirlikGram - 1000) / 1000) * 1800),
  sabit: () => 9000,
  aktarmali: (g) => 6000 + Math.ceil(g.agirlikGram / 1000) * 1200,
};

export const ucret = (tarife, gonderi) => TABLO[tarife.tur](gonderi);
DOSYA
cat > v2/gecici/teslim-gunu.mjs <<'DOSYA'
// v2/gecici/teslim-gunu.mjs — teslim gunu islemi: yeni tur icin satir eklendi
const TABLO = { kademeli: (g) => (g.bolge === 1 ? 1 : 3), sabit: () => 2, aktarmali: () => 5 };

export const teslimGunu = (tarife, gonderi) => TABLO[tarife.tur](gonderi);
DOSYA
echo "v2 kuruldu"
```

```
v2 kuruldu
```

İkinci değişiklik yeni bir **işlem**: gönderinin beyan edilen değerine göre sigorta bedeli.
Üçüncü sürüm ikinciden kopyalanarak kurulur.

```sh
cp -r v2 v3
cat > v3/alttip/tarife.mjs <<'DOSYA'
// v3/alttip/tarife.mjs — ust tipe ucuncu yontem eklendi
export class Tarife {
  ucret(gonderi) { throw new Error(`ucret gerceklestirilmedi: ${gonderi}`); }
  teslimGunu(gonderi) { throw new Error(`teslimGunu gerceklestirilmedi: ${gonderi}`); }
  sigortaBedeli(gonderi) { throw new Error(`sigortaBedeli gerceklestirilmedi: ${gonderi}`); }
}
DOSYA
cat > v3/alttip/kademeli.mjs <<'DOSYA'
// v3/alttip/kademeli.mjs — ucuncu yontem eklendi
import { Tarife } from "./tarife.mjs";

export class Kademeli extends Tarife {
  ucret(g) {
    return g.agirlikGram <= 1000 ? 4500 : 4500 + Math.ceil((g.agirlikGram - 1000) / 1000) * 1800;
  }
  teslimGunu(g) { return g.bolge === 1 ? 1 : 3; }
  sigortaBedeli(g) { return Math.round(g.deger * 0.01); }
}
DOSYA
cat > v3/alttip/sabit.mjs <<'DOSYA'
// v3/alttip/sabit.mjs — ucuncu yontem eklendi
import { Tarife } from "./tarife.mjs";

export class Sabit extends Tarife {
  ucret() { return 9000; }
  teslimGunu() { return 2; }
  sigortaBedeli() { return 0; }
}
DOSYA
cat > v3/alttip/aktarmali.mjs <<'DOSYA'
// v3/alttip/aktarmali.mjs — ucuncu yontem eklendi
import { Tarife } from "./tarife.mjs";

export class Aktarmali extends Tarife {
  ucret(g) { return 6000 + Math.ceil(g.agirlikGram / 1000) * 1200; }
  teslimGunu() { return 5; }
  sigortaBedeli(g) { return Math.round(g.deger * 0.02); }
}
DOSYA
cat > v3/gecici/sigorta-bedeli.mjs <<'DOSYA'
// v3/gecici/sigorta-bedeli.mjs — yeni islem: tek dosya, tum turler
const TABLO = {
  kademeli: (g) => Math.round(g.deger * 0.01),
  sabit: () => 0,
  aktarmali: (g) => Math.round(g.deger * 0.02),
};

export const sigortaBedeli = (tarife, gonderi) => TABLO[tarife.tur](gonderi);
DOSYA
echo "v3 kuruldu"
```

```
v3 kuruldu
```

#### Dokunulan Dosya Sayısı

Ölçüm iki sürüm arasındaki farkı sayar. `diff -rq` hem değişen hem yeni eklenen dosya için
bir satır üretir; sayılan bu satırlardır. Karşılaştırma yalnız üç yolun kendi dizinlerini
kapsar — istemci tarafı üç yolda da aynı biçimde etkilendiği için ölçüme girmez.

```sh
for s in alttip gecici parametrik; do
  echo "$s"
  echo "  yeni tur   -> dokunulan dosya = $(diff -rq v1/$s v2/$s | wc -l | tr -d ' ')"
  echo "  yeni islem -> dokunulan dosya = $(diff -rq v2/$s v3/$s | wc -l | tr -d ' ')"
done
```

```
alttip
  yeni tur   -> dokunulan dosya = 1
  yeni islem -> dokunulan dosya = 4
gecici
  yeni tur   -> dokunulan dosya = 2
  yeni islem -> dokunulan dosya = 1
parametrik
  yeni tur   -> dokunulan dosya = 0
  yeni islem -> dokunulan dosya = 0
```

Sayılar karşıt. Alt tip yolunda yeni tür bir dosyaya, yeni işlem dört dosyaya mal oldu.
Geçici yolda ilişki tersine döndü: yeni tür iki dosya, yeni işlem bir dosya. Parametrik
yolda iki genişleme de sıfır dosyaya mal oldu.

Karşıtlığın nedeni bölünme yönüdür. Alt tip yolu kodu **türe göre** böler; bir türün tüm
işlemleri yan yanadır, bu yüzden tür eklemek ucuz, işlem eklemek pahalıdır. Geçici yol kodu
**işleme göre** böler; bir işlemin tüm türleri yan yanadır, bu yüzden ilişki tersine döner.
Bir tasarım iki yönde birden ucuz olamaz; seçim, hangi yönde daha sık genişleneceğine göre
verilir.

Parametrik yolun sıfırları, o yolun bedava olduğu anlamına gelmez. `enUcuz` iki genişlemeden
de etkilenmedi çünkü türe özgü hiçbir iş yapmıyor; ücreti hesaplayan ölçütü çağıran taraf
veriyor. Parametrik çok biçimlilik türe özgü işi ortadan kaldırmaz, onu kendi dışına iter.
Bu yüzden bir sistemde tek başına değil, diğer ikisiyle birlikte bulunur.

Genişletilmiş sistemin üç türü ve üç işlemi aynı sonuçları üretiyor.

```js
// v3/kosum.mjs — genisletilmis sistem: uc tur, uc islem, ayni sonuclar
import { Kademeli } from "./alttip/kademeli.mjs";
import { Sabit } from "./alttip/sabit.mjs";
import { Aktarmali } from "./alttip/aktarmali.mjs";
import { ucret } from "./gecici/ucret.mjs";
import { teslimGunu } from "./gecici/teslim-gunu.mjs";
import { sigortaBedeli } from "./gecici/sigorta-bedeli.mjs";
import { enUcuz } from "./parametrik/secim.mjs";

const G = { agirlikGram: 4000, bolge: 2, deger: 150000 };
const nesneler = [new Kademeli(), new Sabit(), new Aktarmali()];
const kayitlar = [{ tur: "kademeli" }, { tur: "sabit" }, { tur: "aktarmali" }];

const n = (t) => `${t.ucret(G)}/${t.teslimGunu(G)}/${t.sigortaBedeli(G)}`;
const k = (t) => `${ucret(t, G)}/${teslimGunu(t, G)}/${sigortaBedeli(t, G)}`;

console.log("alttip     ", nesneler.map(n).join(" "));
console.log("gecici     ", kayitlar.map(k).join(" "));
console.log("parametrik ", enUcuz(nesneler, (t) => t.ucret(G)).constructor.name,
  enUcuz(kayitlar, (t) => ucret(t, G)).tur);
```

```sh
node v3/kosum.mjs
```

```
alttip      9900/3/1500 9000/2/0 10800/5/3000
gecici      9900/3/1500 9000/2/0 10800/5/3000
parametrik  Sabit sabit
```

#### Seçim Ölçütü

Ölçüt, sistemin hangi eksende genişlediğidir ve bu tahminle değil geçmişle yanıtlanır: son
dönemde kaç tür, kaç işlem eklendiğine bakılır.

Tür ekseninin işlek olduğu yerlerde — taşıyıcı seçenekleri, ödeme yöntemleri, bildirim
kanalları — işlem kümesi genelde erken kararlaşır ve alt tip yolu ucuza gelir. İşlem
ekseninin işlek olduğu yerlerde — sabit bir tür kümesi üzerinde sürekli yeni rapor, yeni
doğrulama, yeni dışa aktarma — geçici yol ucuza gelir; tür kümesi kapalıysa yeni bir işlem
eklemek tek dosyaya iner.

Bir de yanlış seçimin sinyali vardır. Alt tip yolunda her yeni işlem için dört dosyaya
dokunuluyorsa ve bu her ay tekrarlanıyorsa, tasarımın yanlış eksende bölündüğü ölçülmüş
demektir. Aynı biçimde, geçici yolda tablolara sürekli yeni tür satırı ekleniyor ve bir tür
eklendiğinde bir tabloyu güncellemek unutuluyorsa — o durumda hata çalışma zamanında
`TABLO[tarife.tur]` tanımsız çıkarak görünür — eksen tersine dönmüş demektir.

#### Özet

- Çok biçimliliğin üç biçimi aynı problemi çözebilir: alt tip çalışma zamanı tipine
  yönlendirir, geçici tür etiketine göre tablo seçer, parametrik hiçbir tipe bakmaz.
- Aynı gönderide üç yol da aynı sonucu verdi; karşılaştırma davranış üzerinden değil
  genişleme maliyeti üzerinden yapıldı.
- Yeni bir tür eklendiğinde dokunulan dosya: alt tip 1, geçici 2, parametrik 0.
- Yeni bir işlem eklendiğinde dokunulan dosya: alt tip 4, geçici 1, parametrik 0.
- Karşıtlığın nedeni bölünme yönüdür: alt tip yolu türe göre, geçici yol işleme göre böler;
  bir tasarım iki yönde birden ucuz olamaz.
- Parametrik yolun sıfırları türe özgü işin ortadan kalktığını değil, kodun dışına
  itildiğini gösterir; tek başına değil diğer ikisiyle birlikte kullanılır.

#### Sonraki Adım

Alt tip yolundaki `Tarife` sınıfı iki iş yapıyordu: hangi yöntemlerin bulunacağını bildirmek
ve gerçekleştirilmediğinde hata fırlatmak. Bu iki iş ayrılabilir. Bir soyutlama yalnız
sözleşmeyi bildirebilir, bir başkası sözleşmeyle birlikte ortak gerçekleştirimi de
taşıyabilir. Sonraki ders bu iki aracı — arayüz ile soyut sınıfı — karşılaştırır ve seçimin
kırıldığı noktayı koşturarak gösterir: bir tarife türü iki ayrı soyutlamaya birden ihtiyaç
duyduğunda her düzenin ne yaptığı ve kaç satırın tekrar edildiği ölçülür.

### Arayüzler ve Soyut Sınıflar

Source: https://academia.sh/tr/kurslar/paradigmalar/arayuzler-ve-soyut-siniflar

İki soyutlama aracının seçim ölçütü: ortak davranışın soyut sınıfla ve arayüzle paylaşıldığı iki düzenin kurulması, bir taşıyıcı iki soyutlamaya birden ihtiyaç duyduğunda tek kalıtımın kırıldığının koşturularak gösterilmesi ve tekrar edilen satır sayısının sayılması.

Önceki dersteki `Tarife` sınıfı iki iş yapıyordu: hangi yöntemlerin bulunacağını bildirmek
ve gerçekleştirilmediğinde hata fırlatmak. Bu iki iş ayrılabilir. Bir soyutlama yalnız
sözleşmeyi bildirebilir; bir başkası sözleşmeyle birlikte ortak gerçekleştirimi de
taşıyabilir. İki araç arasındaki seçim buradan doğar.

**Arayüz** (interface), bir tipin hangi yöntemleri sunacağını bildiren, hiçbir gerçekleştirim
taşımayan sözleşmedir; TypeScript kursunda tanıtılan biçim budur. **Soyut sınıf** (abstract
class) ise sözleşmenin yanında ortak gerçekleştirimi de taşır ve alt sınıflara devreder.
Aradaki fark tek satırla özetlenir: bir tip birçok arayüz gerçekleştirebilir, ama yalnız bir
sınıftan türeyebilir.

#### Ortak Davranışı Soyut Sınıfla Paylaşmak

Ücretlendirme kitaplığında taşıyıcılar iki ayrı yetenek taşır. Birincisi ücretlendirilebilir
olmaktır: taşıyıcı gönderi başına birim ücreti bilir, toplamı asgari ücretle sınırlayan
hesap ise ortaktır.

```js
// soyut/ucretli.mjs — soyut sinif: ortak toplam hesabini tasir, birim ucreti alt sinifa birakir
export const ASGARI = 5000;

export class UcretliTasiyici {
  birimUcret(gonderi) { throw new Error(`birimUcret gerceklestirilmedi: ${gonderi.kod}`); }
  toplamUcret(gonderiler) {
    return Math.max(gonderiler.reduce((t, g) => t + this.birimUcret(g), 0), ASGARI);
  }
}
```

İkincisi izlenebilir olmaktır: taşıyıcı bir takip kodunun durum kaydını verir, kayıtlar
arasından en yenisini seçen mantık ise yine ortaktır.

```js
// soyut/izlenebilir.mjs — soyut sinif: son durumu secen ortak mantigi tasir
export class IzlenebilirTasiyici {
  durumKaydi(kod) { throw new Error(`durumKaydi gerceklestirilmedi: ${kod}`); }
  sonDurum(kodlar) {
    const kayitlar = kodlar.map((k) => this.durumKaydi(k)).filter((k) => k !== null);
    return kayitlar.reduce((en, k) => (k.zaman > en.zaman ? k : en), kayitlar[0]);
  }
}
```

Aktarmalı taşıyıcı ikisine birden ihtiyaç duyar: hem ücretlendirilir hem izlenir. Doğrudan
karşılık, iki soyut sınıftan birden türemektir.

```js
// soyut/aktarmali-hatali.mjs — iki soyut siniftan birden turetme denemesi
import { UcretliTasiyici } from "./ucretli.mjs";
import { IzlenebilirTasiyici } from "./izlenebilir.mjs";

export class AktarmaliTasiyici extends UcretliTasiyici, IzlenebilirTasiyici {
  birimUcret(g) { return 6000 + Math.ceil(g.agirlikGram / 1000) * 1200; }
  durumKaydi(kod) { return { kod, durum: "aktarmada", zaman: kod.length }; }
}
```

Aşağıdaki komut bu dosyayı çalıştırır. Çıktının dosya yolunu, yığıt izini ve sürüm satırını
içeren bölümleri ortama bağlı olduğu için ayıklanmıştır; kalan üç satır hatanın kendisidir.

```sh
node soyut/aktarmali-hatali.mjs 2>&1 | sed -n '2,3p;5p'
```

```
export class AktarmaliTasiyici extends UcretliTasiyici, IzlenebilirTasiyici {
                                                      ^
SyntaxError: Unexpected token ','
```

Hata çalışma zamanında değil, dosya derlenirken çıkıyor. Tek kalıtım bir kütüphane kısıtı
değil, dilin dilbilgisine yazılmış bir kısıttır. Aynı kısıt tek kalıtımlı her dilde geçerlidir
ve ortak gerçekleştirimin sınıf hiyerarşisiyle paylaşıldığı her tasarımın üst sınırını
belirler: **bir tip ortak gerçekleştirimi en çok bir kaynaktan devralabilir.**

#### Çıkış Yolu ve Bedeli

Kısıtın önündeki yollardan biri, bir soyut sınıftan türeyip ötekinin ortak kodunu kopyalamaktır.

```js
// soyut/aktarmali.mjs — cikis yolu: bir soyut siniftan turetilir, otekinin ortak kodu kopyalanir
import { UcretliTasiyici } from "./ucretli.mjs";

export class AktarmaliTasiyici extends UcretliTasiyici {
  birimUcret(g) { return 6000 + Math.ceil(g.agirlikGram / 1000) * 1200; }
  durumKaydi(kod) { return { kod, durum: "aktarmada", zaman: kod.length }; }
  sonDurum(kodlar) {
    const kayitlar = kodlar.map((k) => this.durumKaydi(k)).filter((k) => k !== null);
    return kayitlar.reduce((en, k) => (k.zaman > en.zaman ? k : en), kayitlar[0]);
  }
}

export class YarimTasiyici extends UcretliTasiyici {
  durumKaydi(kod) { return { kod, durum: "beklemede", zaman: 0 }; }
}
```

İkinci bir yol, iki soyut sınıfı zincirlemektir: `IzlenebilirTasiyici` sınıfını
`UcretliTasiyici` sınıfından türetmek. Bu, kısıtı aşar ama izlenen her taşıyıcıyı
ücretlendirilebilir yapar; ücretlendirilmeyen bir aktarma noktası izleyicisi de ücret
yöntemlerini devralır. Önceki dersin ölçütüyle bu, sözleşmesi tutmayan bir alt tip üretir.

#### Ortak Davranışı Arayüzle Paylaşmak

İkinci düzende soyutlama yalnız sözleşme bildirir. Sözleşme, gereken yöntem adlarının
listesidir; uyum yapısal olarak denetlenir.

```js
// arayuz/sozlesme.mjs — arayuzler yalnizca gereken yontem adlarini bildirir
export const UCRETLENDIRILEBILIR = ["birimUcret"];
export const IZLENEBILIR = ["durumKaydi"];

export const eksikler = (nesne, ...sozlesmeler) =>
  sozlesmeler.flat().filter((ad) => typeof nesne[ad] !== "function");
```

Ortak davranış kalıtımla değil, taşıyıcıyı argüman alan işlevlerle paylaşılır. Bu işlevler
hiçbir sınıfa bağlı değildir; sözleşmeyi karşılayan her nesneyle çalışır.

```js
// arayuz/ortak.mjs — ortak davranis kalitimla degil, tasiyiciyi argüman alan islevlerle paylasilir
export const ASGARI = 5000;

export const toplamUcret = (tasiyici, gonderiler) =>
  Math.max(gonderiler.reduce((t, g) => t + tasiyici.birimUcret(g), 0), ASGARI);

export function sonDurum(tasiyici, kodlar) {
  const kayitlar = kodlar.map((k) => tasiyici.durumKaydi(k)).filter((k) => k !== null);
  return kayitlar.reduce((en, k) => (k.zaman > en.zaman ? k : en), kayitlar[0]);
}
```

Aktarmalı taşıyıcı iki sözleşmeyi de karşılar ve hiçbir ortak kodu kopyalamaz.

```js
// arayuz/aktarmali.mjs — iki arayuzu birden gerceklestirir; ortak kod kopyalanmaz
export class AktarmaliTasiyici {
  birimUcret(g) { return 6000 + Math.ceil(g.agirlikGram / 1000) * 1200; }
  durumKaydi(kod) { return { kod, durum: "aktarmada", zaman: kod.length }; }
}

export class YarimTasiyici {
  durumKaydi(kod) { return { kod, durum: "beklemede", zaman: 0 }; }
}
```

#### Tekrar Edilen Satır

Ölçüm, somut taşıyıcı dosyasındaki satırlardan kaçının ortak davranış dosyasında birebir
bulunduğunu sayar; yorum, `import` satırları ve çok kısa satırlar sayıma girmez.

```js
// tekrar.mjs — iki duzende tekrar edilen ortak davranis satirlarini sayar
import { readFileSync } from "node:fs";

const satirlar = (yol) => readFileSync(yol, "utf8").split("\n")
  .map((s) => s.trim())
  .filter((s) => s.length > 3 && !s.startsWith("//") && !s.startsWith("import"));

const tekrar = (kaynak, ortak) => {
  const kume = new Set(satirlar(ortak));
  return satirlar(kaynak).filter((s) => kume.has(s)).length;
};

console.log(`soyut sinif duzeni  tekrar edilen satir = ${tekrar("soyut/aktarmali.mjs", "soyut/izlenebilir.mjs")}`);
console.log(`arayuz duzeni       tekrar edilen satir = ${tekrar("arayuz/aktarmali.mjs", "arayuz/ortak.mjs")}`);
```

```sh
node tekrar.mjs
```

```
soyut sinif duzeni  tekrar edilen satir = 3
arayuz duzeni       tekrar edilen satir = 0
```

Üç satır küçük bir sayı gibi görünür, ama sabit değildir. İki yeteneğe birden ihtiyaç duyan
her taşıyıcı aynı üç satırı kopyalar; on taşıyıcılı bir filoda otuz satır olur ve son durum
seçme kuralı değiştiğinde on dosyaya dokunulur. Arayüz düzeninde aynı değişiklik tek
dosyadadır, çünkü ortak kod hiç kopyalanmamıştır.

#### İki Düzen Aynı Sonucu Veriyor

Karşılaştırmanın anlamlı olması için iki düzenin aynı işi yapması gerekir. Aynı koşum,
eksik gerçekleştirimin ne zaman görüldüğünü de ölçer: her iki filoda üçüncü taşıyıcı
`birimUcret` yöntemini gerçekleştirmemiştir.

```js
// kosum.mjs — iki duzenin ayni sonucu vermesi ve eksik gerceklestirimin ne zaman gorulmesi
import { AktarmaliTasiyici as SoyutTam, YarimTasiyici as SoyutYarim } from "./soyut/aktarmali.mjs";
import { AktarmaliTasiyici as ArayuzTam, YarimTasiyici as ArayuzYarim } from "./arayuz/aktarmali.mjs";
import { toplamUcret, sonDurum } from "./arayuz/ortak.mjs";
import { eksikler, UCRETLENDIRILEBILIR, IZLENEBILIR } from "./arayuz/sozlesme.mjs";

const GONDERILER = [{ kod: "TR1", agirlikGram: 4000 }, { kod: "TR2", agirlikGram: 900 }];
const KODLAR = ["TR1", "TR2200", "TR30"];

const s = new SoyutTam();
const a = new ArayuzTam();
console.log(`soyut  toplam = ${s.toplamUcret(GONDERILER)}  son durum = ${s.sonDurum(KODLAR).kod}`);
console.log(`arayuz toplam = ${toplamUcret(a, GONDERILER)}  son durum = ${sonDurum(a, KODLAR).kod}`);

const soyutFilo = [new SoyutTam(), new SoyutTam(), new SoyutYarim()];
let islenen = 0;
let hata = "";
try {
  for (const t of soyutFilo) { t.toplamUcret(GONDERILER); islenen += 1; }
} catch (e) { hata = e.message; }
console.log(`soyut  hatadan once islenen = ${islenen}/3  hata: ${hata}`);

const arayuzFilo = [new ArayuzTam(), new ArayuzTam(), new ArayuzYarim()];
const eksik = arayuzFilo.map((t) => eksikler(t, UCRETLENDIRILEBILIR, IZLENEBILIR));
const uygun = arayuzFilo.filter((_, i) => eksik[i].length === 0);
for (const t of uygun) toplamUcret(t, GONDERILER);
console.log(`arayuz kullanimdan once reddedilen = ${eksik.filter((e) => e.length > 0).length}/3` +
  `  eksik yontem: ${eksik.flat().join(",")}`);
console.log(`arayuz calisma zamani hatasi = 0  islenen = ${uygun.length}/3`);
```

```sh
node kosum.mjs
```

```
soyut  toplam = 18000  son durum = TR2200
arayuz toplam = 18000  son durum = TR2200
soyut  hatadan once islenen = 2/3  hata: birimUcret gerceklestirilmedi: TR1
arayuz kullanimdan once reddedilen = 1/3  eksik yontem: birimUcret
arayuz calisma zamani hatasi = 0  islenen = 2/3
```

İki düzen aynı toplamı ve aynı son durumu verdi. Eksik gerçekleştirimde ayrıldılar: soyut
sınıf düzeninde hata ancak üçüncü taşıyıcı işlenmeye başladığında çıktı ve o noktada iki
taşıyıcı zaten işlenmişti. Arayüz düzeninde eksiklik hiçbir iş yapılmadan önce ada kadar
bildirildi.

Bu fark aracın kendisinden değil, denetimin nerede yapıldığından gelir. Soyut sınıfın
fırlatan yöntemi bir çağrı bekler; sözleşme listesi ise çağrı gerektirmez. Statik tipleme
kullanılan bir ortamda aynı denetim derleme zamanına iner ve koşumda hiç görünmez —
TypeScript kursunda kurulan yapısal tipleme tam olarak bunu yapar.

#### Seçim Ölçütü

Soyut sınıf, ortak gerçekleştirim gerçekten paylaşılıyorsa ve bir tipin tek bir aileye ait
olduğu kesinse tercih edilir; kalıtım hem sözleşmeyi hem kodu tek adımda taşır. Arayüz,
bir tipin birden çok yetenek taşıması bekleniyorsa tercih edilir; ölçülen 3'e karşı 0
satırlık fark ve bunun taşıyıcı sayısıyla çarpılması bu seçimin bedelidir.

İki araç birbirini dışlamaz. Yaygın düzen, sözleşmeyi arayüzle bildirmek ve ortak
gerçekleştirimi bağımsız işlevlerde ya da tek bir yardımcı tipte toplamaktır; böylece
paylaşım kalıtım hattını tüketmez. Bu düzenin adı bileşimdir ve kalıtımla arasındaki seçim
sonraki dersin konusudur.

#### Özet

- Arayüz yalnız sözleşme bildirir, soyut sınıf sözleşmeyle birlikte ortak gerçekleştirimi de
  taşır; bir tip birçok arayüz gerçekleştirebilir ama tek bir sınıftan türeyebilir.
- İki soyut sınıftan birden türeme denemesi çalışma zamanına bile ulaşmadan `SyntaxError`
  ile durdu; tek kalıtım dilin dilbilgisine yazılmış bir kısıttır.
- Kısıtın çıkış yolu ortak kodun kopyalanmasıdır: soyut sınıf düzeninde 3 satır tekrar
  edildi, arayüz düzeninde 0.
- Tekrar sabit değildir; iki yeteneğe birden ihtiyaç duyan her taşıyıcı aynı üç satırı
  çoğaltır ve kural değiştiğinde dokunulan dosya sayısı taşıyıcı sayısı kadar olur.
- İki düzen aynı toplamı ve aynı son durumu üretti; ayrıldıkları yer eksik gerçekleştirimin
  görülme anıydı: soyut sınıfta iki taşıyıcı işlendikten sonra, arayüzde hiç iş yapılmadan.
- İki soyut sınıfı zincirlemek kısıtı aşar ama izlenen her taşıyıcıyı ücretlendirilebilir
  yaparak sözleşmesi tutmayan bir alt tip üretir.

#### Sonraki Adım

Bu derste ortak davranış iki yolla paylaşıldı: kalıtımla ve taşıyıcıyı argüman alan
işlevlerle. İkincisi kalıtım hattını tüketmediği için iki yeteneği bir arada taşıyabildi.
Aynı tercihin bir başka sonucu daha vardır ve o, üst sınıfın kendi içinde bir değişiklik
yaptığında ortaya çıkar. Sonraki ders bunu ölçer: bir üst sınıfta bir yöntemin
gerçekleştirimi değiştiğinde alt sınıfın davranışının sessizce değişmesi bir koşumla
gösterilir, sonra aynı değişiklik bileşimli sürümde denenir.

### Bileşim Kalıtıma Tercih Edilir

Source: https://academia.sh/tr/kurslar/paradigmalar/bilesim-kalitima-tercih-edilir

Kırılgan üst sınıf sorununun ölçülmesi: üst sınıfta kendi davranışını değiştirmeyen bir gerçekleştirim değişikliğinin alt sınıfın sayacını 200 nesnenin 200'ünde sessizce bozması, aynı değişikliğin bileşimli sürümde hiçbir sapma üretmemesi ve bileşimin iletme bedelinin sayılması.

Önceki ders ortak davranışın iki yolla paylaşılabildiğini gösterdi: kalıtımla ve nesneyi
argüman alan işlevlerle. Ölçüm o noktada tekrar edilen satır sayısıydı. Aynı tercihin bir
başka sonucu daha vardır ve bu, üst sınıf kendi içinde bir değişiklik yaptığında ortaya
çıkar. Alt sınıf, üst sınıfın **dışarıya söylediklerine** değil, içeride ne yaptığına da
bağımlı olabilir.

**Kırılgan üst sınıf sorunu** (fragile base class problem), üst sınıfta yapılan ve üst
sınıfın kendi davranışını değiştirmeyen bir gerçekleştirim değişikliğinin alt sınıfların
davranışını bozmasıdır. Bu ders sorunu ölçer, sonra aynı değişikliği bileşimli bir sürümde
dener.

#### Sevkiyat ve İki Sarmalama Yolu

Üst sınıf, kapsülleme dersindeki sevkiyat sınıfının sadeleştirilmiş bir sürümüdür: kapasiteyi
aşmayan gönderileri kabul eder, aşanları reddeder. `hepsiniEkle` yöntemi bir partiyi tek tek
`ekle` yöntemine verir.

```js
// sevkiyat.mjs — ust sinif, ilk surum: hepsiniEkle her ogeyi kendi ekle yontemiyle isler
export class Sevkiyat {
  #gonderiler = []; #toplam = 0; #kapasite;

  constructor(kapasiteGram) { this.#kapasite = kapasiteGram; }

  ekle(gonderi) {
    if (this.#toplam + gonderi.agirlikGram > this.#kapasite) return false;
    this.#gonderiler.push(gonderi);
    this.#toplam += gonderi.agirlikGram;
    return true;
  }

  hepsiniEkle(gonderiler) { for (const g of gonderiler) this.ekle(g); }

  durum() { return { adet: this.#gonderiler.length, toplam: this.#toplam }; }
}
```

Yeni bir ihtiyaç doğar: kabul edilen gönderilerin sayılması. Birinci karşılık kalıtımdır.
Alt sınıf `ekle` yöntemini kendi gerçekleştirimiyle karşılar, üst sürümü çağırır ve kabulleri
sayar.

```js
// sayan-kalitim.mjs — kalitimla sarmalama: alt sinif ekle yontemini kendi surumuyle karsilar
import { Sevkiyat } from "./sevkiyat.mjs";

export class SayanSevkiyat extends Sevkiyat {
  #sayac = 0;
  get sayac() { return this.#sayac; }
  ekle(gonderi) {
    const kabul = super.ekle(gonderi);
    if (kabul) this.#sayac += 1;
    return kabul;
  }
}
```

İkinci karşılık bileşimdir. Sarmalayıcı sevkiyatı içeride tutar, dışarıya aynı yöntemleri
sunar ve `hepsiniEkle` yöntemini kendi `ekle` yöntemi üzerinden yazar.

```js
// sayan-bilesim.mjs — bilesimle sarmalama: sevkiyat iceride tutulur, yontemler iletilir
export class SayanSarmalayici {
  #ic; #sayac = 0;

  constructor(sevkiyat) { this.#ic = sevkiyat; }

  get sayac() { return this.#sayac; }

  ekle(gonderi) {
    const kabul = this.#ic.ekle(gonderi);
    if (kabul) this.#sayac += 1;
    return kabul;
  }

  hepsiniEkle(gonderiler) { for (const g of gonderiler) this.ekle(g); }

  durum() { return this.#ic.durum(); }
}
```

İki sürüm dışarıdan ayırt edilemez. Koşum, sabit tohumlu bir üreteçle 200 parti üretir, her
partiyi iki sarmalayıcıya da verir ve sayaçları bağımsız hesaplanan doğru değerle
karşılaştırır.

```js
// kosum.mjs — ayni parti dizisini iki sarmalama yoluna uygular ve sayaclari dogrular
import { Sevkiyat } from "./sevkiyat.mjs";
import { SayanSevkiyat } from "./sayan-kalitim.mjs";
import { SayanSarmalayici } from "./sayan-bilesim.mjs";

const uretec = (tohum) => {
  let x = tohum;
  return () => (x = (x * 1103515245 + 12345) % 2147483648) / 2147483648;
};

const NESNE = 200;
const KAPASITE = 20000;

const partiler = () => {
  const rast = uretec(20250729);
  return Array.from({ length: NESNE }, () =>
    Array.from({ length: 12 }, (_, i) => ({ id: i, agirlikGram: 500 + Math.floor(rast() * 3500) })));
};

const dogruSayi = (parti) => {
  let toplam = 0, adet = 0;
  for (const g of parti) if (toplam + g.agirlikGram <= KAPASITE) { toplam += g.agirlikGram; adet += 1; }
  return adet;
};

let kalitimYanlis = 0, bilesimYanlis = 0;
const veri = partiler();
for (const parti of veri) {
  const dogru = dogruSayi(parti);
  const k = new SayanSevkiyat(KAPASITE); k.hepsiniEkle(parti);
  const b = new SayanSarmalayici(new Sevkiyat(KAPASITE)); b.hepsiniEkle(parti);
  if (k.sayac !== dogru) kalitimYanlis += 1;
  if (b.sayac !== dogru) bilesimYanlis += 1;
}

const temiz = new Sevkiyat(KAPASITE);
temiz.hepsiniEkle(veri[0]);
const d = temiz.durum();
console.log(`ust sinifin kendi davranisi = adet ${d.adet}, toplam ${d.toplam}`);
console.log(`kalitimli sayac yanlis      = ${kalitimYanlis}/${NESNE}`);
console.log(`bilesimli sayac yanlis      = ${bilesimYanlis}/${NESNE}`);
const yeni = (n) => (typeof n.muhurle === "function" ? "var" : "yok");
console.log(`yeni ust sinif yontemi      = kalitim ${yeni(new SayanSevkiyat(KAPASITE))},` +
  ` bilesim ${yeni(new SayanSarmalayici(new Sevkiyat(KAPASITE)))}`);
```

```sh
node kosum.mjs
```

```
ust sinifin kendi davranisi = adet 9, toplam 18566
kalitimli sayac yanlis      = 0/200
bilesimli sayac yanlis      = 0/200
yeni ust sinif yontemi      = kalitim yok, bilesim yok
```

İki yol da doğru sayıyor. Bu noktada aralarında seçim yapmak için bir gerekçe görünmüyor.

#### Üst Sınıfta Bir Değişiklik

Üst sınıfın bakımını üstlenen taraf `hepsiniEkle` yöntemini yeniden yazar: her öge için
yöntem çağrısı yapmak yerine döngüyü kendi içinde çalıştırır. Aynı sürümde bir de yeni bir
yöntem eklenir — sevkiyatın kapasitesini o anki toplama sabitleyen `muhurle`.

```js
// sevkiyat-v2.mjs — ust sinifin ikinci surumu: hepsiniEkle ic dongusunu kendi icinde yapar
export class Sevkiyat {
  #gonderiler = []; #toplam = 0; #kapasite;

  constructor(kapasiteGram) { this.#kapasite = kapasiteGram; }

  ekle(gonderi) {
    if (this.#toplam + gonderi.agirlikGram > this.#kapasite) return false;
    this.#gonderiler.push(gonderi);
    this.#toplam += gonderi.agirlikGram;
    return true;
  }

  hepsiniEkle(gonderiler) {
    for (const g of gonderiler) {
      if (this.#toplam + g.agirlikGram > this.#kapasite) continue;
      this.#gonderiler.push(g);
      this.#toplam += g.agirlikGram;
    }
  }

  muhurle() { this.#kapasite = this.#toplam; }

  durum() { return { adet: this.#gonderiler.length, toplam: this.#toplam }; }
}
```

Değişiklik üst sınıfın **kendi** davranışını korur: aynı gönderiler kabul edilir, aynı
gönderiler reddedilir, aynı toplam çıkar. Aynı koşum yeni sürümle tekrarlanır.

```sh
cp sevkiyat-v2.mjs sevkiyat.mjs
node kosum.mjs
```

```
ust sinifin kendi davranisi = adet 9, toplam 18566
kalitimli sayac yanlis      = 200/200
bilesimli sayac yanlis      = 0/200
yeni ust sinif yontemi      = kalitim var, bilesim yok
```

Üst sınıfın kendi davranışı birinci satırda birebir aynı: 9 gönderi, 18566 gram. Üst sınıf
için yazılmış her sınama iki sürümde de geçerdi. Alt sınıfın sayacı ise 200 nesnenin
200'ünde yanlışa döndü. Hiçbir istisna fırlamadı, hiçbir uyarı çıkmadı; sayaç sıfırda kaldı.

#### Sapmanın Kaynağı

Alt sınıfın sayacı, üst sınıfın `hepsiniEkle` yöntemi içinde `this.ekle` çağırmasına
bağlıydı. Bu çağrı üst sınıfın belgelenmiş sözleşmesinin parçası değildi; bir gerçekleştirim
ayrıntısıydı. Alt sınıf o ayrıntıya, farkında olmadan, bir bağımlılık kurdu.

Buradaki asıl gözlem şudur: kalıtım, üst sınıfın **iç çağrı örüntüsünü** de sözleşmenin
parçası hâline getirir. Bir üst sınıfın alt sınıflara verdiği güvence yalnız "hangi
yöntemler var, ne döndürürler" değildir; "hangi yöntem hangisini çağırır" da güvencenin
içine girer. Bu ikinci bölüm neredeyse hiçbir zaman yazılmaz ve yazılmadığı için de
korunmaz.

Bileşimli sarmalayıcı bu bağımlılığı hiç kurmadı. Onun `hepsiniEkle` yöntemi kendi `ekle`
yöntemini çağırıyordu; iç nesnenin `hepsiniEkle` yöntemini hiç kullanmadı. İç nesneden
istediği tek şey, açıkça çağırdığı `ekle` ve `durum` yöntemlerinin davranışıydı — yani
belgelenmiş sözleşme. Üst sınıfın iç düzenlemesi değiştiğinde etkilenmemesinin nedeni budur.

#### Bileşimin Bedeli

Son satır bileşimin bedelini gösteriyor. Yeni sürümde eklenen `muhurle` yöntemi kalıtımlı
sürümde kendiliğinden ortaya çıktı, bileşimli sürümde çıkmadı. Sarmalayıcı, iç nesnenin
kazandığı hiçbir yeteneği bedava almaz.

Bedelin ikinci yüzü iletme kodudur. Sarmalayıcı üç yöntemi elle yazdı: `ekle`, `hepsiniEkle`
ve `durum`. İç sınıfın arayüzü on yönteme çıksaydı, sarmalayıcının dışarıya aynı yüzü
sunabilmesi için on iletme yazılması gerekirdi. Kalıtımda bu sayı sıfırdır.

İki bedel karşılaştırılabilir. Kalıtımın bedeli, üst sınıf her değiştiğinde ortaya çıkabilen
ve ölçümde 200/200 olarak görünen sessiz sapmadır. Bileşimin bedeli, arayüz her
genişlediğinde yazılması gereken ve derhâl görünen iletme kodudur. İkincisi sayılabilir ve
öngörülebilir; birincisi ne sayılabilir ne öngörülebilir. Tercihin gerekçesi budur.

#### Kalıtımın Ucuz Kaldığı Durum

Kalıtım her durumda pahalı değildir. Üst sınıf ile alt sınıf aynı ekip tarafından ve aynı
sürümle bakılıyorsa, üst sınıfın iç çağrı örüntüsü değiştiğinde alt sınıflar da gözden
geçirilebilir; sapma sessiz kalmaz. Aynı biçimde, üst sınıf değişmeye kapalıysa — sürümü
sabitlenmiş, davranışı dondurulmuş bir tipse — kırılganlık zemini ortadan kalkar.

Kırılganlık, üst sınıfın alt sınıflardan bağımsız olarak değiştiği durumlarda doğar: farklı
bir ekip, farklı bir sürüm hattı, farklı bir depo. Ölçüt sınıf sayısı ya da hiyerarşi
derinliği değil, **kimin ne zaman değiştirdiğidir**.

#### Özet

- Kırılgan üst sınıf sorunu, üst sınıfın kendi davranışını korumasına rağmen alt sınıfları
  bozan bir gerçekleştirim değişikliğidir.
- Ölçümde üst sınıfın kendi davranışı iki sürümde birebir aynı kaldı (9 gönderi, 18566 gram);
  buna rağmen kalıtımlı sayaç 200 nesnenin 200'ünde yanlışa döndü.
- Aynı değişiklik bileşimli sarmalayıcıda 0 sapma üretti; sarmalayıcı iç nesneden yalnız
  açıkça çağırdığı yöntemlerin davranışına bağımlıydı.
- Kalıtım, üst sınıfın iç çağrı örüntüsünü de sözleşmenin parçası yapar; bu bölüm neredeyse
  hiç yazılmadığı için korunmaz.
- Bileşimin bedeli iletme kodudur ve sayılabilir: sarmalayıcı burada üç yöntem iletti;
  ayrıca üst sınıfa eklenen yeni yöntem bileşimli sürümde kendiliğinden görünmedi.
- Kalıtım, üst sınıfla alt sınıfın birlikte bakıldığı ya da üst sınıfın değişime kapalı
  olduğu durumlarda ucuz kalır; ölçüt kimin ne zaman değiştirdiğidir.

#### Sonraki Adım

Bu ders iki nesnenin birbirine nasıl bağlanacağını ölçtü. Bir sonraki soru bir nesnenin
kendi içinde ne taşıyacağıdır. Sevkiyat sınıfı hem gönderileri hem kapasite kuralını
tutuyordu; kural veriyle aynı yerdeydi. Yaygın bir başka düzen bunu ayırır: veri yalnız
alan taşır, kurallar ayrı işlemci birimlerine dağılır. Sonraki ders iki düzeni karşılaştırır
ve bir kuralın kaç ayrı yerde tekrarlandığını sayar; sonra kural değiştirildiğinde her iki
düzende kaç sonucun birbirinden ayrıldığını ölçer.

### Nesne Sorumluluğu

Source: https://academia.sh/tr/kurslar/paradigmalar/nesne-sorumlulugu

Anemik veri taşıyıcısı ile davranışı veriyle tutan düzenin karşılaştırılması: aynı iki kuralın kaç dosyada yazıldığının sayılması, kural değiştirildiğinde arama tabanlı bir güncellemenin bir yeri atlaması ve 40 gönderinin 20'sinde ortaya çıkan tutarsızlığın ölçülmesi.

Önceki ders iki nesnenin birbirine nasıl bağlanacağını ölçtü. Bu dersin sorusu bir nesnenin
kendi içinde ne taşıyacağıdır. Sevkiyat sınıfı hem gönderileri hem kapasite kuralını
tutuyordu; kural veriyle aynı yerdeydi. Yaygın bir başka düzen bunu ayırır: veri yalnız alan
taşır, kurallar ayrı işlemci birimlerine dağılır.

Alan taşıyan ama hiçbir kural içermeyen nesnelere **anemik** (anemic) denir. Bu ders iki
düzeni aynı kurallar üzerinde karşılaştırır: bir kuralın kaç yerde yazıldığını sayar ve kural
değiştiğinde kaç sonucun birbirinden ayrıldığını ölçer.

#### Anemik Düzen

Gönderi üç alan taşır ve başka hiçbir şey bilmez.

```js
// anemik/gonderi.mjs — yalnizca alan tasiyan veri nesnesi; hicbir kural burada degil
export const gonderi = (agirlikGram, hacimCm3, sozlesmeli = false) =>
  ({ agirlikGram, hacimCm3, sozlesmeli });
```

Kitaplıkta iki kural vardır. Birincisi **ücretli ağırlık**: ücrete esas alınan değer, gerçek
ağırlık ile hacimden türetilen ağırlığın büyüğüdür. İkincisi **sözleşmeli indirimi**:
sözleşmeli müşteride tutar 0,9 katsayısıyla çarpılır. Üç istemci de bu kuralları kendi
içinde uygular.

```js
// anemik/ucretlendirme.mjs — ucretli agirlik ve indirim kurallarini kendisi uygular
export const ucret = (g) => {
  const agirlik = Math.max(g.agirlikGram, g.hacimCm3 / 3);
  const oran = g.sozlesmeli ? 0.9 : 1;
  return { agirlik, tutar: Math.round(agirlik * 1.2 * oran) };
};
```

```js
// anemik/teklif.mjs — ayni iki kurali yeniden yazar
export const tahmin = (g) => {
  const agirlik = Math.max(g.agirlikGram, g.hacimCm3 / 3);
  const oran = g.sozlesmeli ? 0.9 : 1;
  return { agirlik, tutar: Math.round(agirlik * 1.5 * oran) };
};
```

Üçüncü istemci aynı kuralı yazar, ama böleni yerel bir sabite almıştır. Bu, tek başına
kusur sayılmayan sıradan bir yazım tercihidir; dersin ilerleyen bölümünde belirleyici hâle
gelecektir.

```js
// anemik/plan.mjs — ayni kural, boleni yerel bir sabite alinmis bicimde
const HACIM_BOLENI = 3;

export const yuk = (g) => {
  const agirlik = Math.max(g.agirlikGram, g.hacimCm3 / HACIM_BOLENI);
  return { agirlik, dolulukPayi: Math.round((agirlik / 20000) * 100) };
};
```

#### Davranışı Veriyle Tutan Düzen

İkinci düzende iki kural gönderinin kendisine taşınır. Alanlar özeldir; dışarıya kural
sonuçları açılır.

```js
// sorumlu/gonderi.mjs — veri ile onu yorumlayan kural ayni birimde
export class Gonderi {
  #agirlikGram; #hacimCm3; #sozlesmeli;

  constructor(agirlikGram, hacimCm3, sozlesmeli = false) {
    this.#agirlikGram = agirlikGram;
    this.#hacimCm3 = hacimCm3;
    this.#sozlesmeli = sozlesmeli;
  }

  ucretliAgirlik() { return Math.max(this.#agirlikGram, this.#hacimCm3 / 3); }

  indirimOrani() { return this.#sozlesmeli ? 0.9 : 1; }
}
```

Aynı üç istemci artık hesaplamaz, sorar.

```js
// sorumlu/ucretlendirme.mjs — kurali sormaz, gonderiye sorar
export const ucret = (g) => {
  const agirlik = g.ucretliAgirlik();
  return { agirlik, tutar: Math.round(agirlik * 1.2 * g.indirimOrani()) };
};
```

```js
// sorumlu/teklif.mjs — ayni iki soruyu ayni yere sorar
export const tahmin = (g) => {
  const agirlik = g.ucretliAgirlik();
  return { agirlik, tutar: Math.round(agirlik * 1.5 * g.indirimOrani()) };
};
```

```js
// sorumlu/plan.mjs — yalnizca agirligi sorar
export const yuk = (g) => {
  const agirlik = g.ucretliAgirlik();
  return { agirlik, dolulukPayi: Math.round((agirlik / 20000) * 100) };
};
```

#### Kural Kaç Yerde Yazılı

Ölçüm iki kuralı arar ve kaç dosyada göründüklerini sayar; aynı taramada istemcilerin
gönderinin alanlarına kaç kez doğrudan eriştiği de sayılır.

```js
// olcum.mjs — iki kuralin kac dosyada tekrarlandigini ve alanlara kac kez dogrudan erisildigini sayar
import { readdirSync, readFileSync } from "node:fs";

const KURAL = [
  ["ucretli agirlik", /Math\.max\([^)]*hacimCm3/],
  ["sozlesmeli indirimi", /sozlesmeli \? 0\.9/],
];
const ALAN = /\bg\.(agirlikGram|hacimCm3|sozlesmeli)\b/g;

for (const dizin of ["anemik", "sorumlu"]) {
  const dosyalar = readdirSync(dizin).sort().map((d) => [d, readFileSync(`${dizin}/${d}`, "utf8")]);
  console.log(dizin);
  for (const [ad, kalip] of KURAL) {
    const yerler = dosyalar.filter(([, k]) => kalip.test(k)).map(([d]) => d);
    console.log(`  ${ad.padEnd(20)} yazildigi dosya = ${yerler.length}  ${yerler.join(", ")}`);
  }
  const erisim = dosyalar.reduce((t, [, k]) => t + (k.match(ALAN) ?? []).length, 0);
  console.log(`  ${"alana dogrudan erisim".padEnd(20)} = ${erisim}`);
}
```

```sh
node olcum.mjs
```

```
anemik
  ucretli agirlik      yazildigi dosya = 3  plan.mjs, teklif.mjs, ucretlendirme.mjs
  sozlesmeli indirimi  yazildigi dosya = 2  teklif.mjs, ucretlendirme.mjs
  alana dogrudan erisim = 8
sorumlu
  ucretli agirlik      yazildigi dosya = 1  gonderi.mjs
  sozlesmeli indirimi  yazildigi dosya = 1  gonderi.mjs
  alana dogrudan erisim = 0
```

Ücretli ağırlık kuralı anemik düzende üç dosyada, davranışı veriyle tutan düzende bir
dosyada yazılı. Sözleşmeli indirimi ikiye karşı bir. Son satır bunun nedenini gösteriyor:
anemik düzende istemciler gönderinin alanlarına sekiz kez doğrudan erişiyor, öteki düzende
hiç erişmiyor. Alanı okuyan taraf, o alanın nasıl yorumlanacağını da bilmek zorundadır;
kuralın çoğalması bu bilme zorunluluğunun sonucudur.

#### Kural Değiştiğinde

İki düzen de aynı sonucu üretir. Aşağıdaki denetleyici 40 gönderilik bir ızgarada üç
istemciyi çalıştırır ve üçünün bulduğu ücretli ağırlığın aynı olup olmadığını sayar.

```js
// tutarlilik.mjs — uc istemcinin ayni gonderi icin ayni ucretli agirligi bulup bulmadigini sayar
import { gonderi } from "./anemik/gonderi.mjs";
import { ucret as aUcret } from "./anemik/ucretlendirme.mjs";
import { tahmin as aTahmin } from "./anemik/teklif.mjs";
import { yuk as aYuk } from "./anemik/plan.mjs";
import { Gonderi } from "./sorumlu/gonderi.mjs";
import { ucret as sUcret } from "./sorumlu/ucretlendirme.mjs";
import { tahmin as sTahmin } from "./sorumlu/teklif.mjs";
import { yuk as sYuk } from "./sorumlu/plan.mjs";

const izgara = [];
for (const agirlik of [400, 900, 2500, 6000, 11000])
  for (const hacim of [600, 4500, 18000, 45000])
    for (const sozlesmeli of [false, true])
      izgara.push([agirlik, hacim, sozlesmeli]);

const ayrilan = (islemler, kur) => izgara.filter((satir) => {
  const agirliklar = islemler.map((f) => f(kur(satir)).agirlik);
  return new Set(agirliklar).size > 1;
}).length;

console.log(`gonderi sayisi     = ${izgara.length}`);
console.log(`anemik  ayrilan    = ${ayrilan([aUcret, aTahmin, aYuk], (s) => gonderi(...s))}`);
console.log(`sorumlu ayrilan    = ${ayrilan([sUcret, sTahmin, sYuk], (s) => new Gonderi(...s))}`);
```

```sh
node tutarlilik.mjs
```

```
gonderi sayisi     = 40
anemik  ayrilan    = 0
sorumlu ayrilan    = 0
```

Şimdi kural değişir: hacimden ağırlığa çeviren bölen 3'ten 4'e iner. Değişikliği yapan
taraf kuralı arar ve bulduğu her yeri günceller. Aşağıdaki blok bunu yapar; `sed -i`
seçeneği GNU ve BSD sürümlerinde farklı davrandığı için geçici dosya üzerinden yazılmıştır.

```sh
for f in $(grep -rl 'hacimCm3 / 3' anemik sorumlu | sort); do
  sed 's|hacimCm3 / 3|hacimCm3 / 4|' "$f" > "$f.yeni" && mv "$f.yeni" "$f"
  echo "guncellendi: $f"
done
node tutarlilik.mjs
```

```
guncellendi: anemik/teklif.mjs
guncellendi: anemik/ucretlendirme.mjs
guncellendi: sorumlu/gonderi.mjs
gonderi sayisi     = 40
anemik  ayrilan    = 20
sorumlu ayrilan    = 0
```

Arama anemik düzendeki üç yerin ikisini buldu. Üçüncüsü — böleni yerel bir sabite almış
olan `plan.mjs` — aramaya takılmadı ve eski kuralla kaldı. Sonuç, 40 gönderinin 20'sinde
ücretlendirmenin bir ağırlık, yükleme planının başka bir ağırlık kullanmasıdır. Aynı
gönderi iki ayrı ağırlıkla anılmaya başladı ve hiçbir yerde hata çıkmadı.

Davranışı veriyle tutan düzende aynı arama tek yeri buldu, çünkü kural tek yerdeydi.
Ayrılan sonuç sayısı sıfır kaldı.

Buradaki asıl gözlem, aramanın bir yeri kaçırmış olması değildir; her arama er geç bir yeri
kaçırır. Asıl gözlem, anemik düzende **kaçırmanın mümkün olmasıdır**. Kural bir yerdeyse
kaçırılacak ikinci bir yer yoktur.

#### Sorma, Söyle

Ölçümdeki sekiz doğrudan erişim, kuralın çoğalmasının sebebiydi. Bunu tersine çeviren ilkeye
**sorma, söyle** (tell, don't ask) denir: bir nesneden verisini alıp üzerinde karar vermek
yerine, kararı nesneye söyletmek. Anemik istemci `g.hacimCm3` alanını alıp bölüyordu;
sorumlu istemci `g.ucretliAgirlik()` diyerek sonucu istiyordu.

İlkenin sınırı da ölçümde görünür. Bir gönderiye sorulabilecek şey, gönderinin kendi
verisiyle yanıtlayabildiği şeydir. Ücretli ağırlık ve indirim oranı böyledir. Bir tarifeye
göre tutar hesaplamak değildir: bu, gönderinin bilmediği bir ikinci nesneyi gerektirir.
Gönderiye tarifeyi de taşıtmak, sorumluluğu toplamak değil şişirmek olur — kitaplığın her
kuralı tek bir sınıfa yığılır. Ölçüt sabittir: bir kural, yalnız kendi verisiyle
yanıtlanabiliyorsa o verinin yanına konur.

#### Alan Taşıyıcısının Yerinde Olduğu Durum

Anemik düzen her yerde kusur değildir. Bir sınırı geçen kayıtlar — ağdan gelen bir gövde,
bir veritabanı satırından okunan alanlar, dosyaya yazılan bir çıktı — davranış taşımaz ve
taşımamalıdır; işleri biçimi korumaktır, kural yorumlamak değil. Kusur, bu kayıtların
kuralları olan alan nesnelerinin **yerine** geçmesidir: sınırdan gelen kayıt olduğu gibi
sistemin içine yayılırsa, onu yorumlayan her nokta kuralı yeniden yazmak zorunda kalır ve
ölçülen üçe karşı bir tablosu ortaya çıkar.

Ayrım pratikte tek soruyla verilir: bu nesnenin taşıdığı alanlar bir kural tarafından
yorumlanıyor mu? Yorumlanıyorsa kural onunla birlikte durmalıdır. Yorumlanmıyorsa alan
taşıyıcısı yerindedir.

#### Özet

- Anemik nesne yalnız alan taşır, kuralları ayrı işlemcilere bırakır; kuralın veriyle
  birlikte tutulması bunun karşıtıdır.
- Aynı iki kural anemik düzende üç ve iki dosyada yazılıydı; davranışı veriyle tutan düzende
  ikisi de tek dosyadaydı.
- Kuralın çoğalmasının kaynağı ölçüldü: anemik istemciler gönderinin alanlarına sekiz kez
  doğrudan erişiyordu, öteki düzende sıfır.
- Bölen 3'ten 4'e indirildiğinde arama anemik düzendeki üç yerin ikisini buldu; farklı
  yazılmış üçüncüsü eski kuralla kaldı ve 40 gönderinin 20'sinde ücretlendirme ile yükleme
  planı ayrı ağırlık kullandı, hiçbir hata çıkmadı.
- Aynı değişiklik davranışı veriyle tutan düzende tek dosyaya dokundu ve 0 ayrılma üretti.
- Kural nesneye ancak nesnenin kendi verisiyle yanıtlanabiliyorsa taşınır; sınırı geçen
  kayıtların alan taşıyıcısı olarak kalması yerindedir.

#### Sonraki Adım

Bu konu boyunca birim **nesne**ydi: durum ve onu koruyan davranış bir arada tutuldu.
Kapsülleme durumu dışarıdan yazılmaya kapattı, soyutlama temsili gizledi, kalıtım ve bileşim
davranışın nasıl paylaşılacağını belirledi, bu ders de kuralı verinin yanına yerleştirdi.
Hepsinin ortak varsayımı, durumun bir sahibi olduğu ve o sahibin durumu zaman içinde
değiştirdiğidir.

Bir başka model durumu birimden tamamen çıkarır. Veri değişmez; davranış, girdiden çıktıya
bir eşlemedir; yan etki programın sınırına itilir. Sonraki konu bu modeli kurar ve saf
fonksiyonlarla başlar: aynı girdiye her zaman aynı çıktıyı veren, dışarıda hiçbir iz
bırakmayan bir fonksiyonun ne kazandırdığı ve neyin bedelini artırdığı ölçülür.

## Fonksiyonel Programlama

Saf fonksiyonun referans saydamlığıyla sınanması, değiştirilemez veriyle paylaşılan durumun ortadan kaldırılması, davranışın fonksiyon parametresiyle dışarı alınması, boru hattı bileşimi, saf çekirdek ile kirli kabuk ayrımı ve iki paradigmanın aynı sistemde ölçüte bağlı seçimi.

### Saf Fonksiyonlar

Source: https://academia.sh/tr/kurslar/paradigmalar/saf-fonksiyonlar

Yan etkisizlik ve referans saydamlığı: çağrının değeriyle değiştirilmesinin koşturularak sınanması, saf olmayan sürümde davranışın nasıl kaydığı ve iki sürümün testi için gereken düzeneğin satır sayısıyla karşılaştırılması.

Bir önceki ders nesne sorumluluğunu tamamladı: veri ile onu değiştiren davranış aynı
birimde durur, dışarısı duruma değil davranışa seslenir. O konu boyunca birim **nesne**ydi.
Durum vardı, onu koruyan davranış vardı ve ikisi bir arada tutuluyordu.

Bir başka model durumu birimden tamamen çıkarır. Veri üretildikten sonra değiştirilmez,
davranış girdiden çıktıya bir eşlemeye indirgenir, yan etki programın sınırına itilir. Bu
konu o modeli kurar; ilk soru en küçük parçasındadır. Bir fonksiyonun **saf** olması ne
demektir ve saflık, iddia edilmek yerine nasıl koşturulup sınanır?

#### Saf Fonksiyonun İki Koşulu

**Saf fonksiyon (pure function)** Programlama Temelleri kursunda iki koşulla tanımlanmıştı:
aynı girdi için her zaman aynı çıktıyı verir ve **yan etki (side effect)** üretmez. Yan
etki, fonksiyonun döndürdüğü değerin dışında kalan her gözlenebilir değişikliktir: kendi
dışındaki bir değişkeni değiştirmek, dosyaya yazmak, ağ isteği açmak, ekrana yazdırmak.

Gönderi ücretlendirme kitaplığının çekirdeği bu tanıma uyar. Ücret, gönderinin ağırlığından
ve bölgesinden, tarifenin kademelerinden ve bölge katsayılarından hesaplanır.

```js
// ucret.mjs — gonderi ucretlendirmenin cekirdegi: girdiden ciktiya bir esleme
export const TARIFE = {
  ad: "yurtici-standart",
  asgariUcret: 4990,
  kademeler: [
    { ustAgirlikGram: 1000, ucret: 4990 },
    { ustAgirlikGram: 5000, ucret: 6490 },
    { ustAgirlikGram: 15000, ucret: 10900 },
  ],
  bolgeKatsayisi: { 1: 1.0, 2: 1.15, 3: 1.35 },
};

export const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1 },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2 },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3 },
];

// Ucret kurus cinsinden tam sayidir; kayan noktali para hesabi yapilmaz.
export function ucretHesapla(gonderi, tarife) {
  const kademe = tarife.kademeler.find((k) => gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? tarife.kademeler.at(-1);
  const katsayi = tarife.bolgeKatsayisi[gonderi.bolge];
  return Math.max(tarife.asgariUcret, Math.round(kademe.ucret * katsayi));
}
```

İki koşul da fonksiyonun imzasından okunabilir. Tarife bir parametredir, modül düzeyinde
tutulan bir değer değildir; fonksiyon hiçbir yere yazmaz, yalnızca döndürür.

Aynı hesap, sık rastlanan bir biçimde de yazılabilir. Aşağıdaki sürümde tarife dışarıdan
alınmaz, aylık hacim modül düzeyinde biriktirilir ve her çağrı bir kayıt satırı üretir.

```js
// kirli-ucret.mjs — ayni hesap, ama aylik hacim sayaci modul duzeyinde tutuluyor
import { TARIFE } from "./ucret.mjs";

const HACIM_ESIGI = 20000;
let aylikHacim = 0;

export function ucretHesaplaKirli(gonderi) {
  const kademe = TARIFE.kademeler.find((k) => gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? TARIFE.kademeler.at(-1);
  const temel = Math.max(TARIFE.asgariUcret,
    Math.round(kademe.ucret * TARIFE.bolgeKatsayisi[gonderi.bolge]));
  aylikHacim += temel;
  console.log(`[kayit] ${gonderi.kod} ucretlendirildi`);
  return Math.round(temel * (aylikHacim > HACIM_ESIGI ? 0.9 : 1));
}

export const sifirla = () => { aylikHacim = 0; };
```

İkinci sürüm de doğru sonuçlar üretir. Hacim indirimi gerçek bir iş kuralıdır, kayıt satırı
gerçek bir gereksinimdir. Aradaki fark davranışın doğruluğunda değil, davranışın nereye
bağlı olduğundadır: `ucretHesaplaKirli` çağrısının sonucu, kendisinden önce kaç çağrı
yapıldığına bağlıdır.

#### Referans Saydamlığı

Bu bağlılığın ölçülebilir bir sınavı vardır. **Referans saydamlığı (referential
transparency)**, bir ifadenin, ürettiği değerle değiştirilmesi durumunda programın
davranışının değişmemesidir. Sınav yordamı doğrudan bu tanımdan çıkar: aynı çağrı üç kez
yazılır, sonra çağrı bir kez hesaplanıp sonucu üç yere gömülür, iki koşumun sonuçları
karşılaştırılır.

```js
// saydamlik.mjs — cagriyi degeriyle degistirmek davranisi degistiriyor mu
import { TARIFE, GONDERILER, ucretHesapla } from "./ucret.mjs";
import { ucretHesaplaKirli, sifirla } from "./kirli-ucret.mjs";

const g = GONDERILER[1];

const cagriyla = [ucretHesapla(g, TARIFE), ucretHesapla(g, TARIFE), ucretHesapla(g, TARIFE)];
const deger = ucretHesapla(g, TARIFE);
const gomulu = [deger, deger, deger];
console.log(`saf   / cagriyla : ${cagriyla.join(", ")}`);
console.log(`saf   / gomulu   : ${gomulu.join(", ")}`);
console.log(`saf   / esit mi  : ${cagriyla.join() === gomulu.join()}`);

sifirla();
const kCagriyla = [ucretHesaplaKirli(g), ucretHesaplaKirli(g), ucretHesaplaKirli(g)];
sifirla();
const kDeger = ucretHesaplaKirli(g);
const kGomulu = [kDeger, kDeger, kDeger];
console.log(`kirli / cagriyla : ${kCagriyla.join(", ")}`);
console.log(`kirli / gomulu   : ${kGomulu.join(", ")}`);
console.log(`kirli / esit mi  : ${kCagriyla.join() === kGomulu.join()}`);
```

```
saf   / cagriyla : 7463, 7463, 7463
saf   / gomulu   : 7463, 7463, 7463
saf   / esit mi  : true
[kayit] TR-4472 ucretlendirildi
[kayit] TR-4472 ucretlendirildi
[kayit] TR-4472 ucretlendirildi
[kayit] TR-4472 ucretlendirildi
kirli / cagriyla : 7463, 7463, 6717
kirli / gomulu   : 7463, 7463, 7463
kirli / esit mi  : false
```

Çıktıda iki ayrı sapma vardır. Birincisi döndürülen değerlerdedir: saf olmayan sürümde
üçüncü çağrı, biriken hacim eşiği aştığı için 7463 yerine 6717 döndürür. Değişiklik
gönderide değil, çağrı geçmişindedir. İkincisi kayıt satırlarındadır. Toplam dört satır
basılmıştır: üç çağrılı koşumdan üç, tek çağrılı koşumdan bir. İfadeyi değeriyle
değiştirmek yalnızca sonucu değil, programın ürettiği kayıt sayısını da değiştirmiştir.

Referans saydamlığının pratikteki karşılığı üç maddedir. Bir çağrının sonucu önbelleğe
alınabilir, çünkü sonraki çağrılar aynı değeri verir. Çağrılar yeniden sıralanabilir ya da
gereksizse hiç yapılmayabilir, çünkü sıra sonucu etkilemez. Bir hata raporundaki girdi
yeterlidir; hatayı yeniden üretmek için çağrı geçmişini kurmak gerekmez. Saf olmayan
sürümde bu üç maddenin hiçbiri geçerli değildir.

#### Saflığın Testte Görünen Bedeli

Aradaki fark en somut biçimde testte görünür. İki sürüm için aynı üç iddia yazılır: üç
gönderinin ücreti sırasıyla 4990, 7463 ve 14715 olmalıdır.

```js
// saf.test.mjs — uc gonderi, uc iddia; kurulum yalnizca girdinin kendisi
import test from "node:test";
import assert from "node:assert/strict";
import { TARIFE, GONDERILER, ucretHesapla } from "./ucret.mjs";

test("ucret kademe ve bolge katsayisiyla hesaplanir", () => {
  assert.equal(ucretHesapla(GONDERILER[0], TARIFE), 4990);
  assert.equal(ucretHesapla(GONDERILER[1], TARIFE), 7463);
  assert.equal(ucretHesapla(GONDERILER[2], TARIFE), 14715);
});
```

```js
// kirli.test.mjs — ayni uc iddia; sayaci ve kayit akisini testin denetlemesi gerekiyor
import test, { beforeEach, afterEach } from "node:test";
import assert from "node:assert/strict";
import { GONDERILER } from "./ucret.mjs";
import { ucretHesaplaKirli, sifirla } from "./kirli-ucret.mjs";

const gercekLog = console.log;

beforeEach(() => {
  sifirla();
  console.log = () => {};
});

afterEach(() => {
  console.log = gercekLog;
});

test("bir kilograma kadar, birinci bolge", () => {
  assert.equal(ucretHesaplaKirli(GONDERILER[0]), 4990);
});

test("bes kilograma kadar, ikinci bolge", () => {
  assert.equal(ucretHesaplaKirli(GONDERILER[1]), 7463);
});

test("on bes kilograma kadar, ucuncu bolge", () => {
  assert.equal(ucretHesaplaKirli(GONDERILER[2]), 14715);
});
```

```sh
node --test --test-reporter=tap 2>&1 | grep -E '^# (tests|pass|fail)'
for f in saf.test.mjs kirli.test.mjs; do
  printf '%-15s %2d satir\n' "$f" "$(awk 'END{print NR}' "$f")"
done
```

```
# tests 4
# pass 4
# fail 0
saf.test.mjs    10 satir
kirli.test.mjs  28 satir
```

Her iki dosya da geçer, ama aynı üç iddia bir dosyada 10, diğerinde 28 satır tutar. Fazlalık
iddialarda değil **düzenekte (fixture)** birikir. Saf olmayan sürümün testi üç şeyi
yapmak zorundadır: her denemeden önce modül düzeyindeki sayacı sıfırlamak, kayıt akışını
susturup sonra geri koymak ve üç iddiayı ayrı denemelere bölmek. Üçüncüsü isteğe bağlı
değildir; iddialar tek gövdede kalırsa biriken hacim üçüncü iddiayı düşürür.

Bu satırların hepsi, üretimde bulunmayan bir düzeni testte kurmak içindir. Saf sürümde
kurulacak bir düzen yoktur, çünkü fonksiyonun ihtiyacı olan her şey parametrelerindedir.

#### Saflığın Sınırı

Saflık, fonksiyonun içinde hiçbir şeyin değişmemesi demek değildir. Ölçüt
**gözlenebilirliktir**: fonksiyonun gövdesinde tanımlanmış bir değişken kaç kez
değiştirilirse değiştirilsin, dışarıdan görülmediği sürece fonksiyon saftır. Aşağıdaki
döngü yerel bir birikeç kullanır ve saflığı bozmaz.

```js
// toplam.mjs — yerel degisken degisir, disaridan gorunmez: fonksiyon saf kalir
export function toplamUcret(gonderiler, ucretlendir) {
  let toplam = 0;
  for (const g of gonderiler) toplam += ucretlendir(g);
  return toplam;
}
```

İki sınır ayrıca not edilmelidir. Birincisi, saf bir fonksiyon parametre olarak aldığı
nesneyi değiştirmemelidir; değiştirirse çağıranın gördüğü veri de değişir ve bu bir yan
etkidir. Bu kısıtı sağlamanın yolu bir sonraki dersin konusudur. İkincisi, zaman ve
rastgelelik saflığın doğal düşmanlarıdır: `Date.now` çağrısını gövdeye koymak, aynı girdiye
aynı çıktı koşulunu kırar. Çözüm bu değerleri yasaklamak değil, dışarıda üretip parametre
olarak geçirmektir. Kural, ücretlendirme kitaplığının tamamı için tek cümlede yazılabilir:
çekirdek yalnızca kendisine verileni okur.

#### Özet

- Saf fonksiyon iki koşulu sağlar: aynı girdiye aynı çıktı ve yan etki üretmemek. Koşullar
  fonksiyonun imzasından okunur; tarife parametredir, modül değişkeni değildir.
- Referans saydamlığı, bir çağrının değeriyle değiştirilmesinin davranışı değiştirmemesidir.
  Sınav koşturularak yapılır: saf sürümde iki koşum eşit, saf olmayan sürümde üçüncü değer
  7463 yerine 6717 ve kayıt satırı sayısı üç yerine bir çıkar.
- Saf olmayan sürümde önbellekleme, çağrıları yeniden sıralama ve hatayı yalnızca girdiden
  yeniden üretme olanaklarının üçü de kaybolur.
- Aynı üç iddia saf sürümde 10, saf olmayan sürümde 28 satır tutar; fark iddialarda değil,
  sayacı sıfırlayan ve kayıt akışını susturan düzenektedir.
- Fonksiyonun gövdesinde tanımlanmış bir değişkenin değişmesi saflığı bozmaz; ölçüt
  değişikliğin dışarıdan gözlenip gözlenmediğidir.

#### Sonraki Adım

Bu dersin saf fonksiyonu kendi parametresini değiştirmedi, ama bunu bir kural olarak
uygulayan hiçbir şey yoktu. Tarife nesnesi çağıranla paylaşılmış tek bir nesnedir; onu
değiştiren tek bir satır, birbirinden habersiz iki çağrının sonucunu birbirine bağlar.
Sonraki ders paylaşılan bu veriyi ele alıyor: aynı tarifeyi kullanan iki koşum yolunda iç
içe yapının kopyalanmaması kaç sessiz değişikliğe yol açar, kopyalamanın bedeli kaç nesnedir
ve bu bedel nerede ödenmeyebilir.

### Değişmezlik

Source: https://academia.sh/tr/kurslar/paradigmalar/degismezlik

Paylaşılan durumun ortadan kaldırılması: sığ kopyanın bıraktığı sessiz değişikliklerin sayılması, derin kopya ile yapısal paylaşımın ürettiği nesne sayısıyla karşılaştırılması ve kuralın dondurmayla zorlanması.

Önceki ders saf fonksiyonun iki koşulunu kurdu ve `ucretHesapla` bu koşulları sağladı:
tarifeyi parametre olarak aldı, hiçbir yere yazmadı. Ancak fonksiyonun kendi parametresini
değiştirmemesini sağlayan hiçbir şey yoktu. Tarife nesnesi tek bir nesnedir ve onu kullanan
her yer aynı nesneyi görür; bir tek satır onu değiştirdiğinde, birbirinden habersiz iki
çağrının sonucu birbirine bağlanır.

Bu ders o bağı ele alıyor. Ölçülecek üç şey var: paylaşılan veri değiştirildiğinde kaç
ücret sessizce kayar, kopyalamanın bedeli kaç nesnedir ve bu bedelin ne kadarı
ödenmeyebilir.

#### İki Ayrı Terim

Karışması olağan iki terim vardır ve bu platformda ayrı tutulur. **Değiştirilemez
(immutable)** bir değer, üretildikten sonra içeriği değiştirilemeyen değerdir; tarifedeki
bir kademe ücreti buna örnektir. **Değişmez (invariant)** ise her zaman doğru kalması
gereken bir koşuldur; "hesaplanan ücret asgari ücretin altına düşemez" bir değişmezdir.

İlişkileri tek yönlüdür: değiştirilemez veri, değişmezleri korumayı ucuzlatır. Bir değer
kurulduğu anda doğrulanıp bir daha değiştirilemiyorsa, koşulun sonradan bozulup bozulmadığı
sorusu ortadan kalkar. Kapsülleme aynı sonucu başka bir yoldan üretir: durumu gizler ve
yalnızca değişmezi koruyan davranışlara açar. İkisi de aynı soruna, sonradan bozulan
koşullara karşı çalışır.

#### Paylaşılan Tarife

Ücretlendirme kitaplığı tarifeyi yapılandırmadan yükler. Yapı iç içedir: kademeler,
bölge katsayıları, taşıyıcı seçenekleri ve indirim tanımları.

```js
// tarife-verisi.mjs — tarife yapilandirmadan yuklenir; her yukleme taze bir yapi verir
export const tarifeYukle = () => ({
  ad: "yurtici-standart",
  asgariUcret: 4990,
  kademeler: [
    { ustAgirlikGram: 1000, ucret: 4990 },
    { ustAgirlikGram: 5000, ucret: 6490 },
    { ustAgirlikGram: 15000, ucret: 10900 },
  ],
  bolgeKatsayisi: { 1: 1.0, 2: 1.15, 3: 1.35 },
  tasiyicilar: [
    { ad: "hizli", ekUcret: 2500, rota: ["IST", "ANK"] },
    { ad: "ekonomik", ekUcret: 0, rota: ["IST", "ESK", "ANK"] },
    { ad: "teslim-noktasi", ekUcret: -750, rota: ["IST", "ANK", "nokta"] },
  ],
  indirimler: [{ ad: "sozlesmeli", oran: 0.88 }, { ad: "hacim", oran: 0.95 }],
});

export const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1 },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2 },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3 },
  { kod: "TR-4474", agirlikGram: 950, bolge: 2 },
  { kod: "TR-4475", agirlikGram: 4800, bolge: 1 },
  { kod: "TR-4476", agirlikGram: 14900, bolge: 2 },
];

export function ucretHesapla(gonderi, tarife) {
  const kademe = tarife.kademeler.find((k) => gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? tarife.kademeler.at(-1);
  return Math.max(tarife.asgariUcret,
    Math.round(kademe.ucret * tarife.bolgeKatsayisi[gonderi.bolge]));
}
```

Sözleşmeli bir müşteri için kademe ücretlerinin yüzde on iki düşürülmesi isteniyor. Bu,
standart tarifeden yeni bir tarife türetmektir ve üç ayrı yolla yazılabilir.

```js
// turetme.mjs — sozlesmeli tarifeyi ayni kaynaktan turetmenin uc yolu
let uretilen = 0;
const uret = (nesne) => { uretilen++; return nesne; };
export const sayacSifirla = () => { uretilen = 0; };
export const sayacOku = () => uretilen;

// 1. Yerinde degistirme: ust duzey kopyalanir, ic ice yapi paylasilmaya devam eder.
export function sigKopyaylaTuret(tarife, oran) {
  const yeni = uret({ ...tarife });
  for (const k of yeni.kademeler) k.ucret = Math.round(k.ucret * oran);
  return yeni;
}

// 2. Derin kopya: her dugum yeniden uretilir, hicbir sey paylasilmaz.
export function derinKopyaylaTuret(tarife, oran) {
  return uret({
    ...tarife,
    kademeler: uret(tarife.kademeler.map((k) =>
      uret({ ...k, ucret: Math.round(k.ucret * oran) }))),
    bolgeKatsayisi: uret({ ...tarife.bolgeKatsayisi }),
    tasiyicilar: uret(tarife.tasiyicilar.map((t) =>
      uret({ ...t, rota: uret([...t.rota]) }))),
    indirimler: uret(tarife.indirimler.map((i) => uret({ ...i }))),
  });
}

// 3. Yapisal paylasim: yalnizca degisikligin yolu yeniden uretilir, gerisi paylasilir.
export function paylasarakTuret(tarife, oran) {
  return uret({
    ...tarife,
    kademeler: uret(tarife.kademeler.map((k) =>
      uret({ ...k, ucret: Math.round(k.ucret * oran) }))),
  });
}
```

Birinci yol yaygın bir yanılgının ürünüdür: yayma sözdizimiyle bir kopya alındığı için
kaynağın korunduğu sanılır. Alınan kopya **sığ kopyadır**; kök nesne yenidir, ama
`kademeler` dizisi ve içindeki nesneler aynı nesnelerdir. Aynı nesneye iki addan
erişilmesine **takma ad (aliasing)** denir ve sorunun kaynağı budur.

#### Sessiz Değişikliğin Sayılması

Ölçüm iki koşum yolunu yan yana koyar: aynı tarifeyle altı gönderi ücretlendirilir,
sonra sözleşmeli tarife türetilir, sonra aynı altı gönderi yeniden ücretlendirilir. Standart
tarifenin ücretlerinden kaçının değiştiği sayılır.

```js
// olcum.mjs — uc turetme yolunun sessiz degisiklik ve uretilen nesne sayisi
import { tarifeYukle, GONDERILER, ucretHesapla } from "./tarife-verisi.mjs";
import { sigKopyaylaTuret, derinKopyaylaTuret, paylasarakTuret,
  sayacSifirla, sayacOku } from "./turetme.mjs";

const SOZLESME_ORANI = 0.88;
const fiyatlar = (t) => GONDERILER.map((g) => ucretHesapla(g, t));

function olc(ad, turet) {
  const tarife = tarifeYukle();
  const once = fiyatlar(tarife);
  sayacSifirla();
  const sozlesmeli = turet(tarife, SOZLESME_ORANI);
  const uretilenNesne = sayacOku();
  const sonra = fiyatlar(tarife);
  const sessiz = once.filter((u, i) => u !== sonra[i]).length;
  const paylasilan = ["bolgeKatsayisi", "tasiyicilar", "indirimler"]
    .filter((alan) => sozlesmeli[alan] === tarife[alan]).length;
  console.log(`${ad.padEnd(18)} sessiz degisiklik: ${sessiz}/6  ` +
    `uretilen nesne: ${String(uretilenNesne).padStart(2)}  paylasilan alt agac: ${paylasilan}/3`);
  return sonra;
}

const s = olc("sig kopya", sigKopyaylaTuret);
olc("derin kopya", derinKopyaylaTuret);
olc("yapisal paylasim", paylasarakTuret);

console.log(`\nstandart tarife once : ${fiyatlar(tarifeYukle()).join(", ")}`);
console.log(`sig kopyadan sonra   : ${s.join(", ")}`);
```

```
sig kopya          sessiz degisiklik: 5/6  uretilen nesne:  1  paylasilan alt agac: 3/3
derin kopya        sessiz degisiklik: 0/6  uretilen nesne: 16  paylasilan alt agac: 0/3
yapisal paylasim   sessiz degisiklik: 0/6  uretilen nesne:  5  paylasilan alt agac: 3/3

standart tarife once : 4990, 7463, 14715, 5739, 6490, 12535
sig kopyadan sonra   : 4990, 6568, 12949, 5050, 5711, 11031
```

Sığ kopya yolunda altı standart ücretin beşi düşmüştür. Kimse bu ücretleri değiştirmek
istememiştir; tek istenen, ayrı bir sözleşme tarifesi türetmekti. Değişmeyen tek ücret
ilk gönderininkidir ve onu koruyan şey de tasarım değil, asgari ücret tabanının hesabı
4990'da tutmasıdır.

Bu tür bir kusurun iki özelliği vardır. Sessizdir: hiçbir hata fırlamaz, hiçbir kayıt
düşülmez. Uzaktır: kusurun belirtisi standart tarifeyle ücretlendirme yapan yerdedir,
nedeni ise sözleşme tarifesini türeten yerdedir. Aradaki mesafe, iki yerin aynı nesneye
takma adla erişmesinden ibarettir.

#### Kopyalamanın Bedeli

Derin kopya sessiz değişikliği sıfıra indirir, ama on altı nesne üretir. Tarifedeki
değişiklik yalnızca kademelerdedir; bölge katsayıları, üç taşıyıcı, üç rota dizisi ve iki
indirim tanımı olduğu gibi kopyalanmıştır. Kopyalanan bu nesneler asla değiştirilmeyecektir.

Üçüncü yol bu gözlemi kullanır. **Yapısal paylaşım (structural sharing)**, yalnızca
değişikliğin yolu üzerindeki düğümleri yeniden üretir; değişmeyen alt ağaçları başvuruyla
paylaşır. Sonuç aynı korumayı beş nesneyle sağlar: kök, kademe dizisi ve üç kademe.
Paylaşılan alt ağaç sayısı üçtür — türetilen tarifedeki `tasiyicilar` dizisi ile
standarttaki `tasiyicilar` dizisi aynı nesnedir.

Paylaşımın güvenli olmasının koşulu, paylaşılan parçaların da değiştirilmemesidir. Kural
tutarlı biçimde uygulandığında paylaşım bedava korumadır; tek bir yerde bozulduğunda,
paylaşılan alt ağaç sığ kopyadakiyle aynı sessiz kusuru üretir. Ödünleşim burada nettir:
derin kopya kuralın ihlaline dayanıklıdır ve on altı nesne öder, yapısal paylaşım beş nesne
öder ve kuralın her yerde uygulanmasına güvenir.

#### Kuralın Dille Zorlanması

Güvenmek yerine zorlamak da bir seçenektir. **Dondurma (freeze)** JavaScript'te Nesneler ve
Fonksiyonlar kursunda tanıtılmıştı; bir nesnenin özellikleri yazılamaz hâle gelir ve katı
kipte yazma denemesi hata fırlatır. Modüller katı kipte çalıştığı için ek bir ayar
gerekmez.

```js
// dondurma.mjs — degistirilemezligin dille zorlanmasi: sessiz degisiklik hataya donusur
import { tarifeYukle, GONDERILER, ucretHesapla } from "./tarife-verisi.mjs";
import { sigKopyaylaTuret } from "./turetme.mjs";

function derinDondur(nesne) {
  for (const deger of Object.values(nesne)) {
    if (deger !== null && typeof deger === "object") derinDondur(deger);
  }
  return Object.freeze(nesne);
}

const tarife = derinDondur(tarifeYukle());
const once = GONDERILER.map((g) => ucretHesapla(g, tarife));

let hata = 0;
let tur = "-";
try {
  sigKopyaylaTuret(tarife, 0.88);
} catch (e) {
  hata = 1;
  tur = e.constructor.name;
}

const sonra = GONDERILER.map((g) => ucretHesapla(g, tarife));
console.log(`firlatilan hata sayisi : ${hata} (${tur})`);
console.log(`sessiz degisiklik      : ${once.filter((u, i) => u !== sonra[i]).length}/6`);
```

```
firlatilan hata sayisi : 1 (TypeError)
sessiz degisiklik      : 0/6
```

Kod değişmemiştir; aynı `sigKopyaylaTuret` çağrılmıştır. Değişen tek şey, kusurun
belirdiği yerdir: beş sessiz ücret kayması yerine, kusuru üreten satırda bir hata. Hata
türü diller ve çalışma zamanları arasında farklılık gösterir, ama davranışın niteliği
aynıdır — sessiz olan görünür olmuştur.

Dondurmanın da bir bedeli vardır. Derin dondurma her düğümü dolaşır ve her yükleme sırasında
çalışır; büyük yapılandırmalarda bu maliyet ölçülmeden benimsenmemelidir. Yaygın uzlaşma,
dondurmayı yalnızca geliştirme koşumlarında açık tutmak ve üretimde kuralın kod incelemesiyle
sürdürülmesidir.

#### Nerede Ödenmez

Değiştirilemezlik kuralı her satıra uygulanmak zorunda değildir. Ölçüt yine
gözlenebilirliktir: bir fonksiyonun kendi gövdesinde ürettiği ve dışarı sızdırmadığı bir
nesne serbestçe değiştirilebilir. Önceki dersteki `toplamUcret` bunun örneğiydi; birikeç
her adımda değişir, ama fonksiyondan yalnızca son değer çıkar.

Aynı gerekçe toplu kurma için de geçerlidir. Bin gönderilik bir listeyi kurarken her ekleme
için yeni dizi üretmek gereksizdir; dizi yerel olarak doldurulur, dolduktan sonra
dondurulup dışarı verilir. Kural, verinin **paylaşıldığı andan itibaren** değiştirilemez
olmasıdır, üretildiği andan itibaren değil.

#### Özet

- Değiştirilemez (immutable) bir değerin içeriği üretildikten sonra değişmez; değişmez
  (invariant) her zaman doğru kalması gereken bir koşuldur. İlki ikincisini korumayı ucuzlatır.
- Yayma sözdizimiyle alınan kopya sığdır: iç içe yapı takma adla paylaşılmaya devam eder.
  Sözleşme tarifesini bu yolla türetmek altı standart ücretin beşini sessizce düşürmüştür.
- Sessiz kusurun iki özelliği vardır: hiçbir hata üretmez ve belirtisi nedeninden uzaktadır.
- Derin kopya korumayı on altı nesne üreterek sağlar; yapısal paylaşım aynı korumayı beş
  nesneyle sağlar ve üç alt ağacı başvuruyla paylaşır, ama kuralın her yerde uygulanmasına
  güvenir.
- Dondurma güveni zorlamaya çevirir: aynı türetme koşumu sessiz beş kayma yerine bir
  `TypeError` üretir, dolayısıyla kusur kendi satırında görünür.
- Kural paylaşılan veriye uygulanır; bir fonksiyonun gövdesinde üretilip dışarı sızmayan
  yapı serbestçe doldurulabilir.

#### Sonraki Adım

İki ders de veriyi ele aldı: ücret hesabı girdiden çıktıya bir eşlemeye indirgendi, tarife
değiştirilemez hâle getirildi. Davranış ise hâlâ sabittir — `ucretHesapla` tek bir kural
kümesini gövdesinde taşır. Sözleşmeli indirim, hacim indirimi, gece teslimi eki ve
kurumsal anlaşmalar eklendiğinde bu gövde büyür. Sonraki ders davranışın kendisini
parametreye çevirir: aynı ücretlendirme mantığı bayrak parametresiyle, arama tablosuyla ve
fonksiyon parametresiyle yazılır, sonra yeni bir indirim kuralı eklenip her üç sürümde
dokunulan satır sayısı sayılır.

### Yüksek Mertebeden Fonksiyonlar

Source: https://academia.sh/tr/kurslar/paradigmalar/yuksek-mertebeden-fonksiyonlar

Davranışın parametreleşmesi: aynı ücretlendirme kuralının bayrak parametresiyle, arama tablosuyla ve fonksiyon parametresiyle yazılması, yeni bir kural eklendiğinde üç sürümde dokunulan satırın ve çekirdekteki karar noktasının sayılması.

Önceki iki ders veriyi ele aldı: ücret hesabı girdiden çıktıya bir eşlemeye indirgendi,
tarife değiştirilemez hâle getirildi. Davranış ise sabit kaldı. `ucretHesapla` tek bir
kural kümesini kendi gövdesinde taşıyor; sözleşmeli indirim, hacim indirimi, gece teslimi
eki ve müşteriye özel anlaşmalar eklendikçe o gövde büyüyecek.

Bu ders davranışın kendisini parametreye çevirir. Aynı ücretlendirme mantığı üç ayrı yolla
yazılır: bayrak parametresiyle, ad-oran tablosuyla ve fonksiyon parametresiyle. Sonra
gerçek bir gereksinim gelir — gece teslimi eki — ve her üç sürümde dokunulan satır ile
çekirdekte biriken karar noktası sayılır.

#### Ortak Çekirdek

Üç sürüm de aynı temel ücretten başlar. Tarife verisi ve indirimsiz hesap tek bir dosyada
durur; kurallar bu hesabın üzerine gelir.

```js
// tarife.mjs — tarife verisi ve indirimsiz temel ucret
export const TARIFE = {
  ad: "yurtici-standart",
  asgariUcret: 4990,
  kademeler: [
    { ustAgirlikGram: 1000, ucret: 4990 },
    { ustAgirlikGram: 5000, ucret: 6490 },
    { ustAgirlikGram: 15000, ucret: 10900 },
  ],
  bolgeKatsayisi: { 1: 1.0, 2: 1.15, 3: 1.35 },
};

export const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1 },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2 },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3 },
];

export function temelUcret(gonderi, tarife) {
  const kademe = tarife.kademeler.find((k) => gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? tarife.kademeler.at(-1);
  return Math.max(tarife.asgariUcret,
    Math.round(kademe.ucret * tarife.bolgeKatsayisi[gonderi.bolge]));
}
```

Değişen kısım kurallardır: hangi indirimin hangi gönderiye uygulanacağı. Bu karar üç ayrı
yerde verilebilir.

#### Aynı Davranışın Üç Yazılışı

Birinci yol kararı **bayrak parametresine** bırakır. Çağıran hangi kuralın işleyeceğini
mantıksal değerlerle bildirir, kuralın kendisi çekirdeğin gövdesindedir.

```js
// bayrak.mjs — hangi kuralin isleyecegi bayrak parametreleriyle secilir
import { temelUcret } from "./tarife.mjs";

export function ucretBayrakli(gonderi, tarife, secenek = {}) {
  let ucret = temelUcret(gonderi, tarife);
  if (secenek.sozlesmeli) ucret = Math.round(ucret * 0.88);
  if (secenek.hacim) ucret = Math.round(ucret * 0.95);
  return ucret;
}
```

İkinci yol kuralları bir **arama tablosunda** toplar. Çekirdek artık her kural için ayrı
bir dal taşımaz; adı tablodan arar, bulduğu oranı uygular.

```js
// tablo.mjs — kurallar ad-oran tablosunda tutulur, adla secilir
import { temelUcret } from "./tarife.mjs";

const ORANLAR = {
  sozlesmeli: 0.88,
  hacim: 0.95,
};

export function ucretTabloyla(gonderi, tarife, adlar = []) {
  let ucret = temelUcret(gonderi, tarife);
  for (const ad of adlar) ucret = Math.round(ucret * (ORANLAR[ad] ?? 1));
  return ucret;
}
```

Üçüncü yol kuralı bir değere değil, doğrudan bir **fonksiyona** çevirir ve o fonksiyonu
parametre olarak alır.

```js
// kural.mjs — kural bir fonksiyondur, cagirana parametre olarak gecirilir
import { temelUcret } from "./tarife.mjs";

export const sozlesmeli = (ucret) => Math.round(ucret * 0.88);
export const hacim = (ucret) => Math.round(ucret * 0.95);

export function ucretKurallarla(gonderi, tarife, kurallar = []) {
  return kurallar.reduce((ucret, kural) => kural(ucret, gonderi),
    temelUcret(gonderi, tarife));
}
```

Üçüncü sürümdeki `ucretKurallarla` bir **yüksek mertebeden fonksiyondur (higher-order
function)**: Programlama Temelleri kursunda tanımlandığı gibi, ya bir fonksiyonu parametre
olarak alan ya da bir fonksiyon döndüren fonksiyon. Burada iki kez geçer — hem
`ucretKurallarla` kural listesini parametre olarak alır, hem de gövdesindeki `reduce`
kendisine verilen birleştirme fonksiyonunu çağırır.

Aradaki fark biçimsel değildir. Bayraklı sürümde ne yapılacağı çekirdekte yazılıdır,
çağıran yalnızca hangisinin açık olduğunu söyler. Tablolu sürümde ne yapılacağı yine
kitaplığın içindedir, ama tek bir veri yapısında toplanmıştır. Kurallı sürümde ne
yapılacağı çekirdeğin dışındadır; çekirdek yalnızca sırayla uygulamayı bilir.

#### Yeni Bir Kural Eklendiğinde

Gereksinim şu: gece teslimi seçilen gönderilere sabit bir ek uygulanacak, ek 5 kilogramın
altındaki gönderilerde 1500 kuruş, üstündekilerde 2500 kuruş olacak. Bu kural bir çarpan
değildir; toplanan bir tutardır ve gönderinin ağırlığına bakar.

```js
// bayrak2.mjs — bayrakli surume gece teslimi eki eklendi
import { temelUcret } from "./tarife.mjs";

export function ucretBayrakli(gonderi, tarife, secenek = {}) {
  let ucret = temelUcret(gonderi, tarife);
  if (secenek.sozlesmeli) ucret = Math.round(ucret * 0.88);
  if (secenek.hacim) ucret = Math.round(ucret * 0.95);
  if (secenek.gece) ucret += gonderi.agirlikGram > 5000 ? 2500 : 1500;
  return ucret;
}
```

```js
// tablo2.mjs — tablolu surume gece teslimi eki eklendi
import { temelUcret } from "./tarife.mjs";

const ORANLAR = {
  sozlesmeli: { oran: 0.88, ek: 0, agirEk: 0 },
  hacim: { oran: 0.95, ek: 0, agirEk: 0 },
  gece: { oran: 1, ek: 1500, agirEk: 2500 },
};

export function ucretTabloyla(gonderi, tarife, adlar = []) {
  let ucret = temelUcret(gonderi, tarife);
  for (const ad of adlar) {
    const k = ORANLAR[ad] ?? { oran: 1, ek: 0, agirEk: 0 };
    ucret = Math.round(ucret * k.oran);
    ucret += gonderi.agirlikGram > 5000 ? k.agirEk : k.ek;
  }
  return ucret;
}
```

```js
// kural2.mjs — kurallı surume gece teslimi eki eklendi
import { temelUcret } from "./tarife.mjs";

export const sozlesmeli = (ucret) => Math.round(ucret * 0.88);
export const hacim = (ucret) => Math.round(ucret * 0.95);
export const gece = (ucret, g) => ucret + (g.agirlikGram > 5000 ? 2500 : 1500);

export function ucretKurallarla(gonderi, tarife, kurallar = []) {
  return kurallar.reduce((ucret, kural) => kural(ucret, gonderi),
    temelUcret(gonderi, tarife));
}
```

Üç sürümün de aynı sonucu ürettiği koşturularak doğrulanır. Ölçüm dosyası altı çağrıyı yan
yana koyar: önce eski üç sürüm, sonra gece eki eklenmiş üç sürüm.

```js
// olcum.mjs — uc surum ayni sonucu mu veriyor, yeni kural eklenince ne degisiyor
import { TARIFE, GONDERILER } from "./tarife.mjs";
import { ucretBayrakli } from "./bayrak.mjs";
import { ucretTabloyla } from "./tablo.mjs";
import { ucretKurallarla, sozlesmeli, hacim } from "./kural.mjs";
import { ucretBayrakli as bayrakYeni } from "./bayrak2.mjs";
import { ucretTabloyla as tabloYeni } from "./tablo2.mjs";
import { ucretKurallarla as kuralYeni, gece } from "./kural2.mjs";

const her = (f) => GONDERILER.map(f).join(", ");
const satir = (ad, deger) => console.log(`${ad.padEnd(20)} ${deger}`);

satir("bayrak / once", her((g) => ucretBayrakli(g, TARIFE, { sozlesmeli: true, hacim: true })));
satir("tablo  / once", her((g) => ucretTabloyla(g, TARIFE, ["sozlesmeli", "hacim"])));
satir("kural  / once", her((g) => ucretKurallarla(g, TARIFE, [sozlesmeli, hacim])));

const secenek = { sozlesmeli: true, hacim: true, gece: true };
const adlar = ["sozlesmeli", "hacim", "gece"];
satir("bayrak / gece ile", her((g) => bayrakYeni(g, TARIFE, secenek)));
satir("tablo  / gece ile", her((g) => tabloYeni(g, TARIFE, adlar)));
satir("kural  / gece ile", her((g) => kuralYeni(g, TARIFE, [sozlesmeli, hacim, gece])));

// Yalnizca bir cagirani ilgilendiren kural: kitaplikta hicbir dosya degismez.
const ilkGonderi = (ucret, g) => (g.kod === "TR-4471" ? Math.round(ucret * 0.5) : ucret);
satir("kural  / cagirandan", her((g) => kuralYeni(g, TARIFE, [sozlesmeli, hacim, gece, ilkGonderi])));
```

```
bayrak / once        4171, 6239, 12302
tablo  / once        4171, 6239, 12302
kural  / once        4171, 6239, 12302
bayrak / gece ile    5671, 7739, 14802
tablo  / gece ile    5671, 7739, 14802
kural  / gece ile    5671, 7739, 14802
kural  / cagirandan  2836, 7739, 14802
```

İlk altı satır üç sürümün davranışça denk olduğunu gösterir. Son satır yalnızca kurallı
sürümde yazılabilen bir şeyi gösterir: yalnızca `TR-4471` kodlu gönderiyi ilgilendiren bir
indirim, ölçüm dosyasında iki satırla tanımlanıp geçirilmiştir. Kitaplıkta hiçbir dosya
değişmemiştir. Bayraklı ya da tablolu sürümde aynı kuralın çalışması için tek bir müşteriye
ait bir koşulun kitaplığın içine yazılması gerekirdi.

#### Dokunulan Satırın Sayılması

Gece eki eklenirken her sürümde ne kadar yer değiştiğini `diff` sayar. İki ölçüt ayrı
tutulur: dosyanın tamamında dokunulan satır ve **çekirdekte** — yani dışa verilen
ücretlendirme fonksiyonunun kendisinde — dokunulan satır. Üçüncü ölçüt, çekirdekte biriken
karar noktası sayısıdır.

```sh
# Cekirdek: dosyadaki ilk `export function` satirindan dosya sonuna kadar olan bolum.
# Ilk satirdaki dosya adi yorumu olcume katilmaz.
cekirdek() { awk '/^export function/,0' "$1"; }
karar() { cekirdek "$1" | grep -oE '\bif\b|\bfor\b|\?\?|\?' | wc -l | tr -d ' '; }
for s in bayrak tablo kural; do
  toplam=$(diff <(tail -n +2 "$s.mjs") <(tail -n +2 "${s}2.mjs") | grep -c '^[<>]')
  ic=$(diff <(cekirdek "$s.mjs") <(cekirdek "${s}2.mjs") | grep -c '^[<>]')
  printf '%-7s dokunulan: %2d  cekirdekte: %d  karar noktasi: %s -> %s\n' \
    "$s" "$toplam" "$ic" "$(karar "$s.mjs")" "$(karar "${s}2.mjs")"
done
```

```
bayrak  dokunulan:  1  cekirdekte: 1  karar noktasi: 2 -> 4
tablo   dokunulan: 11  cekirdekte: 6  karar noktasi: 2 -> 3
kural   dokunulan:  1  cekirdekte: 0  karar noktasi: 0 -> 0
```

Toplam satır sayısı yanıltıcı olabilir: bayraklı sürüm de kurallı sürüm de tek satırla
büyümüştür. Ayrım ikinci sütundadır. Bayraklı sürümde o satır çekirdeğin gövdesine
girmiştir; karar noktası ikiden dörde çıkmıştır ve her yeni kural bu sayıyı artırmaya devam
edecektir. Kurallı sürümde çekirdek hiç değişmemiştir; eklenen satır çekirdeğin dışında,
bağımsız bir fonksiyondur ve çekirdeğin karar noktası sıfırda kalır.

Tablolu sürüm en pahalı olanıdır: on bir satır dokunulmuş, çekirdeğin gövdesinde altı satır
değişmiştir. Nedeni tablonun **şemasıdır**. Tablo yalnızca oran tutabildiği için, toplanan
bir ek gelince her satıra iki alan daha eklenmiştir — `sozlesmeli` ile `hacim` bu alanları
hiç kullanmasa da. Arama tablosu, tuttuğu değerlerin hepsi aynı biçimde olduğu sürece
ucuzdur; yeni bir kural yeni bir biçim getirdiğinde bedel tablonun tamamına yayılır.

#### Fonksiyon Döndüren Fonksiyon

Yüksek mertebeliliğin ikinci biçimi, fonksiyon döndürmektir. Kuralların hepsi birkaç
kalıptan türediği için, kuralları elle yazmak yerine üreten fonksiyonlar tanımlanabilir.

```js
// uretici.mjs — fonksiyon donduren fonksiyon: yuksek mertebenin ikinci yuzu
import { TARIFE, GONDERILER } from "./tarife.mjs";
import { ucretKurallarla } from "./kural2.mjs";

export const oranli = (oran) => (ucret) => Math.round(ucret * oran);
export const sabitEk = (kurus) => (ucret) => ucret + kurus;
export const kosullu = (kosul, kural) => (ucret, g) => (kosul(g) ? kural(ucret, g) : ucret);

const agir = (g) => g.agirlikGram > 5000;
const kurallar = [
  oranli(0.88),
  oranli(0.95),
  kosullu(agir, sabitEk(2500)),
  kosullu((g) => !agir(g), sabitEk(1500)),
];

console.log(GONDERILER.map((g) => ucretKurallarla(g, TARIFE, kurallar)).join(", "));
```

```
5671, 7739, 14802
```

Sonuç, elle yazılmış üç kuralın ürettiği değerlerle birebir aynıdır. Fark üretim
biçimindedir: üç satırlık üç üreticiden — `oranli`, `sabitEk` ve `kosullu` — sınırsız
sayıda kural kurulabilir. `kosullu` ayrıca bir kuralı başka bir kuralla sarmalar; aldığı
kuralı çağırıp çağırmamaya karar verir. Kuralların bu biçimde birbirinin üstüne
yığılabilmesi bir sonraki dersin konusudur.

#### Dolaylılığın Bedeli

Fonksiyon parametresi bedelsiz değildir. "Sözleşmeli bir müşterinin gönderisinde ne
oluyor" sorusunun yanıtı bayraklı sürümde tek dosyadadır: `bayrak2.mjs` okunur, üç dal
görülür, iş biter. Kurallı sürümde yanıt iki dosyaya dağılmıştır — kuralın tanımı
`kural2.mjs` içinde, hangi kuralların hangi sırayla uygulandığı `olcum.mjs` içindedir.
Okunacak dosya sayısı birden ikiye çıkar ve çağrı yerlerinin sayısı arttıkça sıranın nerede
kurulduğu sorusu her çağrı yerine ayrı ayrı sorulur.

Ölçüt bu yüzden kuralın sayısı ve kaynağıdır. Kural kümesi kapalı ve kısa ise — iki üç
sabit seçenek, hepsi kitaplığın kendi bilgisi — bayrak daha az dolaylılıkla aynı işi
görür. Kural kümesi açık ise, yani kitaplığın önceden bilemeyeceği kurallar çağıranlardan
gelecekse, fonksiyon parametresi çekirdeği hiç değiştirmeden bunu karşılayan tek sürümdür.

#### Özet

- Yüksek mertebeden fonksiyon, bir fonksiyonu parametre olarak alan ya da bir fonksiyon
  döndüren fonksiyondur; `ucretKurallarla` birinci biçimi, `oranli` ikinci biçimi gösterir.
- Aynı davranış üç yolla yazıldığında sonuçlar birebir aynıdır: üç gönderi için 4171, 6239
  ve 12302.
- Gece eki eklendiğinde bayraklı sürümde çekirdekte 1 satır değişir ve karar noktası 2'den
  4'e çıkar; kurallı sürümde çekirdekte 0 satır değişir ve karar noktası 0'da kalır.
- Arama tablosu, yeni kural tablonun şemasına uymadığında en pahalı sürümdür: 11 satır
  dokunulmuş, çekirdekte 6 satır değişmiştir.
- Yalnızca bir çağıranı ilgilendiren kural, kurallı sürümde iki satırla tanımlanıp geçirilir
  ve kitaplıkta hiçbir dosya değişmez.
- Fonksiyon parametresinin bedeli dolaylılıktır: bir gönderiye ne olduğunu anlamak için
  okunacak dosya sayısı birden ikiye çıkar.

#### Sonraki Adım

Bu dersin kuralları tek tek uygulandı; `reduce` her birini sırayla çağırdı ve ara sonuçlar
hiçbir yerde adlandırılmadı. Kural sayısı arttıkça asıl soru sıranın kendisi olur:
indirimin ekten önce mi sonra mı işlediği, asgari ücret tabanının nerede uygulandığı sonucu
değiştirir. Sonraki ders bu ardışık uygulamayı bir boru hattı olarak kurar: adımlar
adlandırılır, her adım tek tek sınanır ve iki adımın yeri değiştirildiğinde kaç gönderinin
ücretinin kaç kuruş kaydığı sayılır.

### Bileşim

Source: https://academia.sh/tr/kurslar/paradigmalar/bilesim

Küçük fonksiyonlardan büyük davranış kurma: ücretlendirmenin dört adımlık bir boru hattına ayrılması, aynı kusurun iki tasarımda kaç testi düşürdüğünün sayılması ve iki adımın yeri değiştiğinde sonucun kaç kuruş kaydığının ölçülmesi.

Önceki ders kuralları fonksiyona çevirdi ve `reduce` ile sırayla uyguladı. Ara sonuçlar
hiçbir yerde adlandırılmadı: kural listesi bir dizi olarak geçti, çekirdek onları arka
arkaya çağırdı. Kural sayısı arttıkça asıl soru sıranın kendisi olur — indirimin ekten önce
mi sonra mı işlediği, asgari ücret tabanının nerede uygulandığı sonucu değiştirir.

Bu ders o ardışık uygulamayı bir boru hattı olarak kurar. Ücretlendirme dört adlandırılmış
adıma bölünür, her adım tek başına sınanır, sonra aynı kusur iki tasarıma da sokulup kaç
testin düştüğü sayılır. Son ölçüm sıraya aittir: iki adımın yeri değiştiğinde kaç gönderinin
ücreti kaç kuruş kayar.

#### İki Ayrı Bileşim

**Bileşim (composition)** bu kursta iki ayrı anlamda geçer ve ikisi karıştırılmamalıdır.
Nesne bileşimi, bir nesnenin başka bir nesneyi üye olarak tutup ona iş devretmesidir; aynı
kursun nesneye dayalı konusunda kalıtımın karşısına konan seçenek odur. Bu dersteki
**fonksiyon bileşimi (function composition)** ise iki fonksiyonun birinin çıktısını
diğerinin girdisi yaparak tek bir fonksiyon oluşturmasıdır. Ortak yan, büyük davranışın
küçük parçalardan kurulmasıdır; birleştirilen şeyin ne olduğu farklıdır — biri nesne, diğeri
fonksiyondur.

#### Adımların Ayrılması

Ücretlendirme dört karardan oluşur: ağırlık kademesinin seçilmesi, bölge katsayısının
uygulanması, sözleşme indirimi ve asgari ücret tabanı. Her karar ayrı bir fonksiyona
alınır. Adımların ortak bir biçimi vardır: hepsi bir **hesap** kaydı alır, aynı biçimde bir
hesap kaydı döndürür. Kayıt değiştirilmez; her adım yayma sözdizimiyle yeni bir kayıt üretir.

```js
// adim.mjs — tarife verisi ve boru hattinin adimlari; her adim hesap alir, hesap dondurur
export const TARIFE = {
  asgariUcret: 4990,
  kademeler: [
    { ustAgirlikGram: 1000, ucret: 4990 },
    { ustAgirlikGram: 5000, ucret: 6490 },
    { ustAgirlikGram: 15000, ucret: 10900 },
  ],
  bolgeKatsayisi: { 1: 1.0, 2: 1.15, 3: 1.35 },
};

export const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1, sozlesmeli: true },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2, sozlesmeli: true },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3, sozlesmeli: false },
  { kod: "TR-4474", agirlikGram: 950, bolge: 2, sozlesmeli: true },
  { kod: "TR-4475", agirlikGram: 4800, bolge: 1, sozlesmeli: false },
  { kod: "TR-4476", agirlikGram: 14900, bolge: 2, sozlesmeli: true },
];

export const kademeSec = (h) => ({
  ...h,
  ucret: (h.tarife.kademeler.find((k) => h.gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? h.tarife.kademeler.at(-1)).ucret,
});

export const bolgeCarpani = (h) => {
  const katsayi = h.tarife.bolgeKatsayisi[h.gonderi.bolge];
  return { ...h, ucret: Math.round(h.ucret * katsayi) };
};

export const sozlesmeIndirimi = (h) =>
  h.gonderi.sozlesmeli ? { ...h, ucret: Math.round(h.ucret * 0.88) } : h;

export const asgariTaban = (h) => ({
  ...h,
  ucret: Math.max(h.tarife.asgariUcret, h.ucret),
});
```

Adımları birleştiren şey ayrı ve çok kısadır. `hat`, aldığı adımları soldan sağa uygulayan
bir fonksiyon döndürür; önceki dersteki kural uygulayıcısıyla aynı `reduce` iskeletidir, ama
bu kez ürettiği şey bir sayı değil, çağrılabilir bir hattır.

```js
// hat.mjs — adimlari ardisik uygulayan bilesim ve kurulmus ucretlendirme hatti
import { kademeSec, bolgeCarpani, sozlesmeIndirimi, asgariTaban } from "./adim.mjs";

export const hat = (...adimlar) => (baslangic) =>
  adimlar.reduce((hesap, adim) => adim(hesap), baslangic);

export const ucretlendir = hat(kademeSec, bolgeCarpani, sozlesmeIndirimi, asgariTaban);
```

Aynı dört karar tek bir gövdede de yazılabilir. Karşılaştırmanın ölçülebilir olması için
denk bir sürüm gerekir.

```js
// tekparca.mjs — ayni dort adim tek fonksiyonun govdesinde
export function ucretlendirTekParca(gonderi, tarife) {
  const kademe = tarife.kademeler.find((k) => gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? tarife.kademeler.at(-1);
  const katsayi = tarife.bolgeKatsayisi[gonderi.bolge];
  let ucret = Math.round(kademe.ucret * katsayi);
  if (gonderi.sozlesmeli) ucret = Math.round(ucret * 0.88);
  return Math.max(tarife.asgariUcret, ucret);
}
```

#### Ara Adımların Sınanması

İki tasarımın farkı testte görünür. Boru hattında her adım dışa verilmiş bir fonksiyondur;
testi, o adımın okuduğu birkaç alandan ibaret küçük bir hesap kaydıyla yazılır.

```js
// hat.test.mjs — dort adim tek tek, hat bir kez sinanir
import test from "node:test";
import assert from "node:assert/strict";
import { TARIFE, GONDERILER, kademeSec, bolgeCarpani, sozlesmeIndirimi,
  asgariTaban } from "./adim.mjs";
import { ucretlendir } from "./hat.mjs";

const hesap = (gonderi, ucret = 0) => ({ gonderi, tarife: TARIFE, ucret });

test("kademe secimi ust siniri kapsar", () => {
  assert.equal(kademeSec(hesap({ agirlikGram: 1000 })).ucret, 4990);
  assert.equal(kademeSec(hesap({ agirlikGram: 1001 })).ucret, 6490);
});

test("bolge carpani ucreti olcekler", () => {
  assert.equal(bolgeCarpani(hesap({ bolge: 3 }, 10000)).ucret, 13500);
});

test("sozlesme indirimi yalnizca sozlesmeli gonderiye iner", () => {
  assert.equal(sozlesmeIndirimi(hesap({ sozlesmeli: true }, 10000)).ucret, 8800);
  assert.equal(sozlesmeIndirimi(hesap({ sozlesmeli: false }, 10000)).ucret, 10000);
});

test("asgari taban altina inilmez", () => {
  assert.equal(asgariTaban(hesap({}, 3000)).ucret, 4990);
});

test("hat alti gonderiyi uctan uca ucretlendirir", () => {
  assert.deepEqual(GONDERILER.map((g) => ucretlendir(hesap(g)).ucret),
    [4990, 6567, 14715, 5050, 6490, 11031]);
});
```

Tek parça sürümde aynı dört kararın hiçbirine doğrudan erişilemez. Testler aynı adları
taşıyabilir, ama hepsi uçtan uca çağrıdır: her biri tam bir gönderi ile tarifeyi kurar ve
yalnızca son sayıya bakar.

```js
// tekparca.test.mjs — ayni dort karar, yalnizca uctan uca sinanabiliyor
import test from "node:test";
import assert from "node:assert/strict";
import { TARIFE, GONDERILER } from "./adim.mjs";
import { ucretlendirTekParca } from "./tekparca.mjs";

test("kademe secimi ust siniri kapsar", () => {
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 1000, bolge: 1, sozlesmeli: false }, TARIFE), 4990);
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 1001, bolge: 1, sozlesmeli: false }, TARIFE), 6490);
});

test("bolge carpani ucreti olcekler", () => {
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 12400, bolge: 3, sozlesmeli: false }, TARIFE), 14715);
});

test("sozlesme indirimi yalnizca sozlesmeli gonderiye iner", () => {
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 3200, bolge: 2, sozlesmeli: true }, TARIFE), 6567);
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 3200, bolge: 2, sozlesmeli: false }, TARIFE), 7463);
});

test("asgari taban altina inilmez", () => {
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 800, bolge: 1, sozlesmeli: true }, TARIFE), 4990);
});

test("alti gonderi uctan uca ucretlendirilir", () => {
  assert.deepEqual(GONDERILER.map((g) => ucretlendirTekParca(g, TARIFE)),
    [4990, 6567, 14715, 5050, 6490, 11031]);
});
```

Her iki dosya da beş deneme içerir ve ikisi de geçer; son denemelerin aynı altı sayıyı
beklemesi, iki tasarımın davranışça denk olduğunu gösterir.

#### Kusurun Yerini Testin Söylemesi

Fark, testler geçerken değil, bir test düştüğünde ortaya çıkar. Aynı kusur iki tasarıma da
sokulur: bölge katsayısı bir yerine iki kez uygulanır. Kusur, kaynak dosyaların birer
kopyasına metin değişikliğiyle sokulur; asıl dosyalar bozulmaz.

```sh
# Ayni kusur iki tasarima da sokulur: bolge katsayisi iki kez uygulaniyor.
perl -pe 's/h\.ucret \* katsayi\)/h.ucret * katsayi * katsayi)/' adim.mjs > adim-bozuk.mjs
perl -pe 's/kademe\.ucret \* katsayi\)/kademe.ucret * katsayi * katsayi)/' \
  tekparca.mjs > tekparca-bozuk.mjs
perl -pe 's{\./adim\.mjs}{./adim-bozuk.mjs}' hat.mjs > hat-bozuk.mjs
perl -pe 's{\./adim\.mjs}{./adim-bozuk.mjs}; s{\./hat\.mjs}{./hat-bozuk.mjs}' \
  hat.test.mjs > hat-bozuk.test.mjs
perl -pe 's{\./tekparca\.mjs}{./tekparca-bozuk.mjs}' \
  tekparca.test.mjs > tekparca-bozuk.test.mjs

for t in hat tekparca; do
  cikti=$(node --test --test-reporter=tap "$t-bozuk.test.mjs" 2>&1)
  echo "--- $t"
  printf '%s\n' "$cikti" | grep -E '^# (tests|fail)'
  printf '%s\n' "$cikti" | grep '^not ok' | sed 's/^not ok [0-9]* - /  basarisiz: /'
done
```

```
--- hat
# tests 5
# fail 2
  basarisiz: bolge carpani ucreti olcekler
  basarisiz: hat alti gonderiyi uctan uca ucretlendirir
--- tekparca
# tests 5
# fail 3
  basarisiz: bolge carpani ucreti olcekler
  basarisiz: sozlesme indirimi yalnizca sozlesmeli gonderiye iner
  basarisiz: alti gonderi uctan uca ucretlendirilir
```

Sayılar üç şey söyler. Boru hattında beş denemenin ikisi düşer; düşenlerden biri kusurlu
adımı adıyla gösterir, diğeri uçtan uca denemedir. Tek parça sürümde üç deneme düşer ve
düşenlerden biri sözleşme indirimini sınayan denemedir — indirimde hiçbir kusur olmadığı
hâlde. Neden şudur: o denemenin girdisi ikinci bölgeye ait olduğu için, bozulan çarpma o
yoldan da geçer.

Üçüncüsü, bölge birinci olan denemelerin ikisinin de geçmesidir. Birinci bölgenin katsayısı
1,0 olduğundan iki kez uygulanmak sonucu değiştirmez; kusur o yolda görünmez. Bu, uçtan uca
denemelerin ortak zayıflığıdır: her deneme yolun tamamından geçer, dolayısıyla düşen deneme
kümesi kusurun yerini değil, girdilerin hangi yollara denk geldiğini anlatır.

#### Sıranın Sonuca Etkisi

Boru hattında adımlar birbirinin çıktısını okuduğu için sıra sonucun parçasıdır. Sözleşme
indirimi ile asgari ücret tabanının yeri değiştirilip iki koşum karşılaştırılır.

```js
// sira.mjs — ayni dort adim, iki farkli sira: sonuc kac gonderide kayiyor
import { TARIFE, GONDERILER, kademeSec, bolgeCarpani, sozlesmeIndirimi,
  asgariTaban } from "./adim.mjs";
import { hat } from "./hat.mjs";

const indirimOnce = hat(kademeSec, bolgeCarpani, sozlesmeIndirimi, asgariTaban);
const tabanOnce = hat(kademeSec, bolgeCarpani, asgariTaban, sozlesmeIndirimi);

const calis = (f) => GONDERILER.map((g) => f({ gonderi: g, tarife: TARIFE, ucret: 0 }).ucret);
const a = calis(indirimOnce);
const b = calis(tabanOnce);
const degisen = a.filter((u, i) => u !== b[i]).length;
const fark = a.reduce((t, u, i) => t + Math.abs(u - b[i]), 0);
const tabanAltinda = b.filter((u) => u < TARIFE.asgariUcret).length;

console.log(`indirim -> taban : ${a.join(", ")}`);
console.log(`taban -> indirim : ${b.join(", ")}`);
console.log(`degisen ucret    : ${degisen}/6   toplam fark: ${fark} kurus`);
console.log(`asgari altinda   : ${tabanAltinda}/6`);
```

```
indirim -> taban : 4990, 6567, 14715, 5050, 6490, 11031
taban -> indirim : 4391, 6567, 14715, 5050, 6490, 11031
degisen ucret    : 1/6   toplam fark: 599 kurus
asgari altinda   : 1/6
```

Altı gönderiden yalnızca birinin ücreti değişmiştir ve fark 599 kuruştur. Sayının küçüklüğü
aldatıcıdır: değişen tek gönderi, tabanın koruduğu gönderidir. Taban indirimden önce
uygulandığında indirim tabanı deler ve sonuç asgari ücretin altına iner. Kurumun kuralı —
hesaplanan ücret asgari ücretin altına düşemez — altı gönderiden birinde çiğnenmiştir.

Buradan iki sonuç çıkar. Birincisi, sıra bir uygulama ayrıntısı değil, iş kuralının
parçasıdır; taban, kendisinden sonra ücreti düşüren hiçbir adım olmayacak biçimde
konumlanmalıdır. İkincisi, hattın kendisi bir sınama konusudur: adımların hepsi tek tek
doğru olduğu hâlde hat yanlış olabilir. Adım testlerinin yanına uçtan uca bir denemenin
konmasının nedeni budur.

#### Bileşimin Ödettiği

Ortak kayıt biçimi bileşimi mümkün kılan şeydir, ama aynı zamanda adımlar arasındaki tek
bağdır. Dört adımın hepsi `gonderi`, `tarife` ve `ucret` alanlarını tanıyan bir kayda
bağlıdır; yeni bir adım yeni bir alan istediğinde o alan kaydın biçimine girer ve kendisine
ihtiyaç duymayan adımların da taşıdığı bir yük olur. Kayıt büyüdükçe hangi adımın hangi
alanı okuduğu görünmez hâle gelir.

İkinci bedel ara değerlerin gözlenmesidir. Tek parça sürümde hata ayıklanırken `ucret`
değişkeninin her satırdaki değeri okunabilir. Boru hattında ara değer hiçbir yerde
adlandırılmadığı için, iki adımın arasına yazdıran bir izleme adımı sokulması gerekir. Bunun
maliyeti düşüktür — izleme adımı da hesabı olduğu gibi döndüren bir adımdır ve listeye
eklenir — ama o adım saf değildir ve hattın içinde kalıcı olarak bırakılmamalıdır. Yan
etkilerin nereye konacağı bir sonraki dersin konusudur.

#### Özet

- Fonksiyon bileşimi, bir fonksiyonun çıktısını diğerinin girdisi yapmaktır; nesne
  bileşiminden farkı, birleştirilen şeyin nesne değil fonksiyon olmasıdır.
- Ücretlendirme dört adıma bölündüğünde her adım kendi küçük girdisiyle sınanabilir; tek
  parça sürümde aynı dört karar yalnızca uçtan uca çağrıyla sınanabilir.
- Aynı kusur iki tasarıma sokulduğunda boru hattında beş denemenin ikisi, tek parça sürümde
  üçü düşer; tek parça sürümde düşenlerden biri kusurla ilgisiz bir kuralı sınayan denemedir.
- Birinci bölgeye ait iki deneme her iki tasarımda da geçer: uçtan uca denemelerin düşme
  örüntüsü kusurun yerini değil, girdilerin denk geldiği yolları gösterir.
- Adımların sırası sonucun parçasıdır: taban ile indirimin yeri değiştiğinde altı gönderiden
  birinin ücreti 599 kuruş kayar ve asgari ücret kuralı bir gönderide çiğnenir.
- Bileşimin bedeli ortak kayıt biçimidir: her yeni alan, ona ihtiyaç duymayan adımların da
  taşıdığı bir yüktür.

#### Sonraki Adım

Bu dersin dört adımı da saftır: hiçbiri okumadığı bir şeye bakmaz, hiçbiri dışarı yazmaz.
Gerçek bir ücretlendirme koşumu ise saf değildir — gönderiler bir dosyadan okunur, gece
tarifesinin geçerli olup olmadığı saate bakılarak belirlenir, sonuçlar bir kayıt dosyasına
yazılır. Sonraki ders bu üç bağımlılığın nereye konacağını ele alır: girdi/çıktı ve zaman
programın sınırına itildiğinde çekirdeğin testi için gereken sahte bağımlılık sayısı, iç içe
düzenle karşılaştırmalı olarak sayılır.

### Yan Etkilerin Sınıra İtilmesi

Source: https://academia.sh/tr/kurslar/paradigmalar/yan-etkilerin-sinira-itilmesi

Saf çekirdek, kirli kabuk düzeni: dosya okuma, saat ve kayıt yazmanın hesabın içinden dış katmana taşınması, iki düzende sahte bağımlılık gerektiren deneme sayısının ve dış dünyaya dokunan satır sayısının karşılaştırılması.

Önceki dersin dört adımı da saftı: hiçbiri okumadığı bir şeye bakmadı, hiçbiri dışarı
yazmadı. Gerçek bir ücretlendirme koşumu ise saf değildir. Gönderiler bir dosyadan okunur,
gece tarifesinin geçerli olup olmadığı saate bakılarak belirlenir, sonuçlar bir dosyaya ve
bir kayıt akışına yazılır.

Bu üç bağımlılık ortadan kaldırılamaz; bir programın işe yaraması için dış dünyayla
konuşması gerekir. Kaldırılabilecek olan, bunların ücret kurallarıyla **aynı yerde**
durmasıdır. Bu ders o ayrımı kurar ve iki düzeni ölçer: çekirdeğin sınanması için kaç sahte
bağımlılık gerekiyor, dış dünyaya dokunan satırlar hangi dosyalarda toplanıyor.

#### Serpiştirilmiş Düzen

İlk düzen doğal olanıdır: koşum baştan sona yazılır, her şey geldiği yerde yapılır. Tarife
verisi iki tasarımın da ortak girdisidir ve ayrı bir dosyada durur.

```js
// tarife.mjs — iki tasarimin da kullandigi tarife verisi
export const TARIFE = {
  asgariUcret: 4990,
  geceEki: 1500,
  kademeler: [
    { ustAgirlikGram: 1000, ucret: 4990 },
    { ustAgirlikGram: 5000, ucret: 6490 },
    { ustAgirlikGram: 15000, ucret: 10900 },
  ],
  bolgeKatsayisi: { 1: 1.0, 2: 1.15, 3: 1.35 },
};
```

```js
// icice.mjs — dosya okuma, saat ve kayit yazma hesabin icine serpistirilmis
import fs from "node:fs";
import { TARIFE } from "./tarife.mjs";

export function gunlukKosum(girdiYolu, ciktiYolu, kayitYolu) {
  const gonderiler = JSON.parse(fs.readFileSync(girdiYolu, "utf8"));
  const saat = new Date(Date.now()).getUTCHours();
  const satirlar = [];
  let toplam = 0;
  for (const g of gonderiler) {
    const kademe = TARIFE.kademeler.find((k) => g.agirlikGram <= k.ustAgirlikGram)
      ?? TARIFE.kademeler.at(-1);
    let ucret = Math.round(kademe.ucret * TARIFE.bolgeKatsayisi[g.bolge]);
    if (g.sozlesmeli) ucret = Math.round(ucret * 0.88);
    if (saat >= 20 || saat < 6) ucret += TARIFE.geceEki;
    ucret = Math.max(TARIFE.asgariUcret, ucret);
    satirlar.push(`${g.kod},${ucret}`);
    toplam += ucret;
    fs.appendFileSync(kayitYolu, `${g.kod} ucretlendirildi\n`);
  }
  fs.writeFileSync(ciktiYolu, `${satirlar.join("\n")}\n`);
  fs.appendFileSync(kayitYolu, `toplam ${toplam}\n`);
  return toplam;
}
```

Bu gövdede beş satır dış dünyaya dokunur: bir okuma, bir saat sorgusu, iki kayıt ekleme ve
bir yazma. Kalan satırlar ücret kurallarıdır. İkisi iç içe geçtiği için kurallara ulaşmanın
tek yolu koşumun tamamını çalıştırmaktır.

#### Saf Çekirdek, Kirli Kabuk

İkinci düzen aynı işi iki parçaya böler. **Çekirdek**, kararı veren saf fonksiyondur:
gönderileri, tarifeyi ve saati parametre olarak alır; yazılacak satırları, düşülecek kayıt
satırlarını ve toplamı **döndürür**. Hiçbirini kendisi yapmaz.

```js
// cekirdek.mjs — saf cekirdek: gonderiler, tarife ve saat disaridan verilir
export function planla({ gonderiler, tarife, saat }) {
  const gece = saat >= 20 || saat < 6;
  const satirlar = [];
  const kayitlar = [];
  let toplam = 0;
  for (const g of gonderiler) {
    const kademe = tarife.kademeler.find((k) => g.agirlikGram <= k.ustAgirlikGram)
      ?? tarife.kademeler.at(-1);
    let ucret = Math.round(kademe.ucret * tarife.bolgeKatsayisi[g.bolge]);
    if (g.sozlesmeli) ucret = Math.round(ucret * 0.88);
    if (gece) ucret += tarife.geceEki;
    ucret = Math.max(tarife.asgariUcret, ucret);
    satirlar.push(`${g.kod},${ucret}`);
    kayitlar.push(`${g.kod} ucretlendirildi`);
    toplam += ucret;
  }
  return { satirlar, kayitlar: [...kayitlar, `toplam ${toplam}`], toplam };
}
```

Kritik ayrım döndürülen değerdedir. Çekirdek "şu satırlar yazılsın" diye bir **tanım**
üretir; yazma işini yapmaz. Kayıt satırları da metin olarak döner, akışa basılmaz. Saat bir
sayıdır, sorgulanan bir kaynak değildir.

**Kabuk** bu tanımı alır ve gerçekleştirir. Okuma, saat ve yazma yalnızca burada geçer.

```js
// kabuk.mjs — kirli kabuk: okuma, saat ve yazma yalnizca bu dosyada
import fs from "node:fs";
import { TARIFE } from "./tarife.mjs";
import { planla } from "./cekirdek.mjs";

export function gunlukKosum(girdiYolu, ciktiYolu, kayitYolu) {
  const gonderiler = JSON.parse(fs.readFileSync(girdiYolu, "utf8"));
  const saat = new Date(Date.now()).getUTCHours();
  const plan = planla({ gonderiler, tarife: TARIFE, saat });
  fs.writeFileSync(ciktiYolu, `${plan.satirlar.join("\n")}\n`);
  fs.appendFileSync(kayitYolu, `${plan.kayitlar.join("\n")}\n`);
  return plan.toplam;
}
```

Kabuğun içinde tek bir iş kuralı yoktur: okur, çağırır, yazar. Karar veren her satır
çekirdektedir, karar vermeyen her satır kabuktadır.

#### Testin Ödediği Bedel

Fark testte sayılabilir hâle gelir. Serpiştirilmiş düzende ücret kurallarına ulaşmanın tek
yolu `gunlukKosum` çağrısıdır; o çağrı da dosya okur, saate bakar ve dosya yazar. Bunların
dördü de denemede yerine sahtesi konarak susturulmak zorundadır.

```js
// icice.test.mjs — her deneme dort sahte bagimlilik kurar
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import { gunlukKosum } from "./icice.mjs";

const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1, sozlesmeli: true },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2, sozlesmeli: true },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3, sozlesmeli: false },
];

function sahteOrtamda(gonderiler, saatUTC, is) {
  const asil = { oku: fs.readFileSync, yaz: fs.writeFileSync,
    ekle: fs.appendFileSync, saat: Date.now };
  const yazilan = { csv: "", kayit: [] };
  fs.readFileSync = () => JSON.stringify(gonderiler);
  fs.writeFileSync = (_, icerik) => { yazilan.csv = icerik; };
  fs.appendFileSync = (_, icerik) => { yazilan.kayit.push(icerik); };
  Date.now = () => Date.UTC(2024, 0, 15, saatUTC);
  try {
    return { sonuc: is(), yazilan };
  } finally {
    fs.readFileSync = asil.oku;
    fs.writeFileSync = asil.yaz;
    fs.appendFileSync = asil.ekle;
    Date.now = asil.saat;
  }
}

test("gunduz kosumunda gece eki uygulanmaz", () => {
  // SAHTE ORTAM
  const { sonuc, yazilan } = sahteOrtamda(GONDERILER, 10,
    () => gunlukKosum("g.json", "c.csv", "k.log"));
  assert.equal(sonuc, 26272);
  assert.equal(yazilan.csv, "TR-4471,4990\nTR-4472,6567\nTR-4473,14715\n");
});

test("gece kosumunda her gonderiye sabit ek biner", () => {
  // SAHTE ORTAM
  const { sonuc } = sahteOrtamda(GONDERILER, 22,
    () => gunlukKosum("g.json", "c.csv", "k.log"));
  assert.equal(sonuc, 30173);
});

test("indirim asgari ucret tabanini delemez", () => {
  // SAHTE ORTAM
  const tek = [{ kod: "TR-4477", agirlikGram: 500, bolge: 1, sozlesmeli: true }];
  const { sonuc } = sahteOrtamda(tek, 10, () => gunlukKosum("g.json", "c.csv", "k.log"));
  assert.equal(sonuc, 4990);
});
```

Ayrılmış düzende aynı üç kural doğrudan `planla` çağrısıyla sınanır. Sahte ortam yalnızca
kabuğun kendi denemesinde kurulur: kabuğun sınanacak tek davranışı, okuduğunu çekirdeğe
verip döndüğünü yazmasıdır.

```js
// kabuk-duzen.test.mjs — ucret kurallari sahtesiz sinanir, sahte yalnizca kablolamada
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import { TARIFE } from "./tarife.mjs";
import { planla } from "./cekirdek.mjs";
import { gunlukKosum } from "./kabuk.mjs";

const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1, sozlesmeli: true },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2, sozlesmeli: true },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3, sozlesmeli: false },
];

test("gunduz kosumunda gece eki uygulanmaz", () => {
  const plan = planla({ gonderiler: GONDERILER, tarife: TARIFE, saat: 10 });
  assert.equal(plan.toplam, 26272);
  assert.deepEqual(plan.satirlar, ["TR-4471,4990", "TR-4472,6567", "TR-4473,14715"]);
});

test("gece kosumunda her gonderiye sabit ek biner", () => {
  assert.equal(planla({ gonderiler: GONDERILER, tarife: TARIFE, saat: 22 }).toplam, 30173);
});

test("indirim asgari ucret tabanini delemez", () => {
  const tek = [{ kod: "TR-4477", agirlikGram: 500, bolge: 1, sozlesmeli: true }];
  assert.equal(planla({ gonderiler: tek, tarife: TARIFE, saat: 10 }).toplam, 4990);
});

test("kabuk okudugunu cekirdege verir, dondugunu yazar", () => {
  // SAHTE ORTAM
  const asil = { oku: fs.readFileSync, yaz: fs.writeFileSync,
    ekle: fs.appendFileSync, saat: Date.now };
  let csv = "";
  fs.readFileSync = () => JSON.stringify(GONDERILER);
  fs.writeFileSync = (_, icerik) => { csv = icerik; };
  fs.appendFileSync = () => {};
  Date.now = () => Date.UTC(2024, 0, 15, 10);
  try {
    assert.equal(gunlukKosum("g.json", "c.csv", "k.log"), 26272);
    assert.equal(csv, "TR-4471,4990\nTR-4472,6567\nTR-4473,14715\n");
  } finally {
    fs.readFileSync = asil.oku;
    fs.writeFileSync = asil.yaz;
    fs.appendFileSync = asil.ekle;
    Date.now = asil.saat;
  }
});
```

#### İki Düzenin Ölçülmesi

Ölçüm iki tabloya ayrılır. Birincisi kaynak dosyalarda dış dünyaya dokunan satırları sayar:
dosya sistemi çağrısı ya da saat okuyan satırlar. İkincisi deneme dosyalarını sayar; sahte
ortam kuran denemeler gövdelerindeki işaretten bulunur.

```sh
# Dis dunyaya dokunan satir: dosya sistemi cagrisi ya da saat okuyan satir.
printf '%-22s %5s %10s\n' dosya satir "dis-dunya"
for f in icice.mjs kabuk.mjs cekirdek.mjs; do
  printf '%-22s %5d %10d\n' "$f" "$(awk 'END{print NR}' "$f")" \
    "$(grep -cE 'fs\.[a-zA-Z]|Date\.now' "$f")"
done
echo
# Sahteli deneme: govdesinde `// SAHTE ORTAM` isareti tasiyan deneme.
printf '%-22s %6s %7s %5s %6s\n' "deneme dosyasi" deneme sahteli satir gecen
for f in icice.test.mjs kabuk-duzen.test.mjs; do
  gecen=$(node --test --test-reporter=tap "$f" 2>&1 | grep '^# pass' | tr -dc '0-9')
  printf '%-22s %6d %7d %5d %6s\n' "$f" "$(grep -c '^test(' "$f")" \
    "$(grep -c 'SAHTE ORTAM' "$f")" "$(awk 'END{print NR}' "$f")" "$gecen"
done
```

```
dosya                  satir  dis-dunya
icice.mjs                 24          5
kabuk.mjs                 13          4
cekirdek.mjs              19          0

deneme dosyasi         deneme sahteli satir  gecen
icice.test.mjs              3       3    51      3
kabuk-duzen.test.mjs        4       1    48      4
```

Birinci tablodaki asıl sayı sondaki sıfırdır. Ücret kurallarını taşıyan on dokuz satırlık
dosyanın dış dünyaya dokunan hiçbir satırı yoktur. Serpiştirilmiş düzende aynı kurallar,
dışarıyla konuşan beş satırla aynı gövdededir; kuralı çalıştırmadan o beş satırdan
kaçınmanın yolu yoktur.

İkinci tablo bunun testteki karşılığıdır. Ayrılmış düzen bir deneme **fazla** içerir, çünkü
kabuğun kablolaması ayrıca sınanmaktadır; buna rağmen sahte ortam kuran deneme sayısı üçten
bire inmiştir. Fark oranlarda daha nettir: serpiştirilmiş düzende denemelerin yüzde yüzü,
ayrılmış düzende yüzde yirmi beşi sahte kurar.

Asıl kazanç ölçeklenmededir. Yeni bir ücret kuralı — hacim indirimi, kurumsal anlaşma, ikinci
bir bölge kademesi — ayrılmış düzende sahte ortam kurmayan bir deneme daha ekler; sahteli
deneme sayısı birde kalır. Serpiştirilmiş düzende her yeni kural denemesi dört sahteyi
yeniden kurar ve bozar. Sahte bağımlılık, sınanan kurala değil, kurala ulaşmak için geçilmesi
gereken yola ödenen bir bedeldir.

#### Sınırın Denetlenmesi

Bu düzenin uygulamada bozulma biçimi tektir: acele bir düzeltmede çekirdeğe bir kayıt satırı
ya da bir saat sorgusu eklenir. İyi yanı, ihlalin ölçülebilir olmasıdır. Ölçüm betiğindeki
ilk tablo bir kurala çevrilebilir — çekirdek dosyalarının "dış-dünya" sütunu sıfır kalmalıdır
— ve bu koşul her değişiklikte yeniden sayılabilir.

Sınırın nereden geçtiğini belirleyen ölçüt, önceki derslerdekiyle aynıdır: gözlenebilirlik.
Çekirdek kendi içinde birikeç değiştirir, dizi doldurur, yerel değişken atar; bunların hiçbiri
dışarıdan görülmez. Dışarıdan görülen üç şey vardır ve üçü de kabuktadır: dosya sistemi,
zaman ve kayıt akışı. Rastgelelik ile ağ çağrısı da aynı kümeye girer.

Kabuğun kendisi saf değildir ve saflaştırılamaz. Buradaki hedef onu **küçültmektir**: on üç
satır, dördü dış dünyaya dokunan, hiçbir iş kuralı içermeyen bir dosya. Bir kusur belirdiğinde
soru ikiye ayrılabilir hâle gelir — yanlış olan hesap mı, yoksa kablolama mı. Serpiştirilmiş
düzende bu soru ayrılamaz.

#### Özet

- Girdi/çıktı, zaman ve kayıt akışı ortadan kaldırılamaz; kaldırılabilecek olan, bunların
  ücret kurallarıyla aynı gövdede durmasıdır.
- Saf çekirdek kararı verir ve ne yapılacağının tanımını döndürür; kirli kabuk o tanımı
  gerçekleştirir. Karar veren her satır çekirdekte, karar vermeyen her satır kabuktadır.
- Çekirdek dosyasının dış dünyaya dokunan satır sayısı sıfırdır; serpiştirilmiş düzende aynı
  kurallar dışarıyla konuşan beş satırla aynı gövdededir.
- Ayrılmış düzende deneme sayısı üçten dörde çıkarken sahte ortam kuran deneme sayısı üçten
  bire iner; yeni her ücret kuralı sahteli deneme sayısını artırmaz.
- Sınır ihlali ölçülebilir bir koşula çevrilir: çekirdek dosyalarında dosya sistemi çağrısı
  ya da saat okuyan satır sayısı sıfır kalmalıdır.
- Kabuk saflaştırılamaz, küçültülür: on üç satır, hiç iş kuralı yok. Böylece bir kusurda
  hesap ile kablolama birbirinden ayrı sorulabilir.

#### Sonraki Adım

Bu konu boyunca kurulan model tek bir birim tanıdı: fonksiyon. Kursun ilk konusu ise başka
bir birim kurmuştu — durumu ve onu koruyan davranışı bir arada tutan nesne. İki model aynı
alanı, gönderi ücretlendirmeyi, farklı biçimlerde böler. Son ders ikisini yan yana koyar:
aynı problem hem nesne ağırlıklı hem fonksiyon ağırlıklı modellenir, sonra iki ayrı değişiklik
istenir ve her birinin iki modelde kaç dosyaya dokunduğu sayılır. Çıkacak sonuç bir üstünlük
ilanı değil, hangi ölçüte göre hangisinin seçildiğidir.

### Paradigma Karışımı

Source: https://academia.sh/tr/kurslar/paradigmalar/paradigma-karisimi

Aynı sistemde iki modelin birlikte kullanımı: taşıyıcı seçeneklerinin nesne ağırlıklı ve fonksiyon ağırlıklı iki modeli, yeni bir işlem ile yeni bir türün her iki modelde kaç dosyaya dokunduğunun sayılması ve seçimin ölçüte bağlanması.

Önceki konu bir birim tanıdı: durumu ve onu koruyan davranışı bir arada tutan nesne. Bu konu
başka bir birim kurdu: girdiden çıktıya eşleme yapan, durumu dışarıda bırakan fonksiyon. İki
model aynı alanı — gönderi ücretlendirmeyi — farklı yerlerden böler.

Bu ders ikisini yan yana koyar. Aynı problem iki kez modellenir, sonra iki ayrı değişiklik
istenir ve her birinin iki modelde kaç dosyaya dokunduğu sayılır. Sonuç bir üstünlük ilanı
değildir; hangi ölçüte göre hangisinin seçildiğidir.

#### Aynı Problemin İki Modeli

Problem şudur: üç taşıyıcı seçeneği vardır — hızlı, ekonomik ve teslim noktası — ve her biri
üzerinde iki işlem tanımlıdır: ücreti hesaplamak ve teslim süresini tahmin etmek.

Nesne ağırlıklı model, taşıyıcıyı birim alır. Her tür kendi dosyasında durur ve her ikisini
de kendisi bilir.

```sh
mkdir -p nesne
cat > gonderiler.mjs <<'EOF'
export const GONDERILER = [
  { kod: "TR-4471", temelUcret: 4990, mesafeKm: 450, tasiyici: "hizli" },
  { kod: "TR-4472", temelUcret: 6567, mesafeKm: 1200, tasiyici: "ekonomik" },
  { kod: "TR-4473", temelUcret: 14715, mesafeKm: 320, tasiyici: "nokta" },
];
EOF
cat > nesne/hizli.mjs <<'EOF'
export class Hizli {
  ucret(g) { return g.temelUcret + 2500; }
  sure(g) { return Math.ceil(g.mesafeKm / 600) + 1; }
}
EOF
cat > nesne/ekonomik.mjs <<'EOF'
export class Ekonomik {
  ucret(g) { return g.temelUcret; }
  sure(g) { return Math.ceil(g.mesafeKm / 350) + 2; }
}
EOF
cat > nesne/nokta.mjs <<'EOF'
export class Nokta {
  ucret(g) { return g.temelUcret - 750; }
  sure(g) { return Math.ceil(g.mesafeKm / 350) + 3; }
}
EOF
cat > nesne/kayit.mjs <<'EOF'
import { Hizli } from "./hizli.mjs";
import { Ekonomik } from "./ekonomik.mjs";
import { Nokta } from "./nokta.mjs";

export const TASIYICILAR = {
  hizli: new Hizli(), ekonomik: new Ekonomik(), nokta: new Nokta(),
};
EOF
cat > nesne/kullan.mjs <<'EOF'
import { GONDERILER } from "../gonderiler.mjs";
import { TASIYICILAR } from "./kayit.mjs";

for (const g of GONDERILER) {
  const t = TASIYICILAR[g.tasiyici];
  console.log(`${g.kod} ${t.ucret(g)} kurus ${t.sure(g)} gun`);
}
EOF
node nesne/kullan.mjs
```

```
TR-4471 7490 kurus 2 gun
TR-4472 6567 kurus 6 gun
TR-4473 13965 kurus 4 gun
```

Fonksiyon ağırlıklı model işlemi birim alır. Taşıyıcı yalnızca bir addır; her işlem kendi
dosyasında durur ve bütün türleri kendisi bilir.

```sh
mkdir -p fonk
cat > fonk/veri.mjs <<'EOF'
export const TASIYICILAR = ["hizli", "ekonomik", "nokta"];
EOF
cat > fonk/ucret.mjs <<'EOF'
export function ucret(g) {
  switch (g.tasiyici) {
    case "hizli": return g.temelUcret + 2500;
    case "ekonomik": return g.temelUcret;
    case "nokta": return g.temelUcret - 750;
    default: throw new Error(`bilinmeyen tasiyici: ${g.tasiyici}`);
  }
}
EOF
cat > fonk/sure.mjs <<'EOF'
export function sure(g) {
  switch (g.tasiyici) {
    case "hizli": return Math.ceil(g.mesafeKm / 600) + 1;
    case "ekonomik": return Math.ceil(g.mesafeKm / 350) + 2;
    case "nokta": return Math.ceil(g.mesafeKm / 350) + 3;
    default: throw new Error(`bilinmeyen tasiyici: ${g.tasiyici}`);
  }
}
EOF
cat > fonk/kullan.mjs <<'EOF'
import { GONDERILER } from "../gonderiler.mjs";
import { TASIYICILAR } from "./veri.mjs";
import { ucret } from "./ucret.mjs";
import { sure } from "./sure.mjs";

for (const g of GONDERILER) {
  if (TASIYICILAR.includes(g.tasiyici) === false) throw new Error(g.tasiyici);
  console.log(`${g.kod} ${ucret(g)} kurus ${sure(g)} gun`);
}
EOF
node fonk/kullan.mjs
```

```
TR-4471 7490 kurus 2 gun
TR-4472 6567 kurus 6 gun
TR-4473 13965 kurus 4 gun
```

İki model aynı sayıları üretir. Fark davranışta değil, kodun hangi eksende bölündüğündedir.
Nesne modeli türe göre böler: bir dosya bir taşıyıcının her şeyini bilir. Fonksiyon modeli
işleme göre böler: bir dosya bir işlemin her tür için nasıl yapıldığını bilir. Aynı bilgi
kümesi, iki farklı yönde dilimlenmiştir.

#### Yeni Bir İşlem Eklendiğinde

Birinci değişiklik yeni bir işlemdir: her gönderi için tahmini karbon salımı, gram cinsinden,
taşıyıcıya bağlı bir katsayıyla hesaplanacak. Ölçüm çağrı yerlerini dışarıda bırakır; yeni bir
işlemin çağrılması her iki modelde de yeni bir çağrı yeri gerektirir.

```sh
cp -r nesne nesne-b && cp -r fonk fonk-b
# Nesne modeli: yeni islem her tur dosyasina ayri ayri eklenir.
cat > nesne-b/hizli.mjs <<'EOF'
export class Hizli {
  ucret(g) { return g.temelUcret + 2500; }
  sure(g) { return Math.ceil(g.mesafeKm / 600) + 1; }
  karbon(g) { return g.mesafeKm * 210; }
}
EOF
cat > nesne-b/ekonomik.mjs <<'EOF'
export class Ekonomik {
  ucret(g) { return g.temelUcret; }
  sure(g) { return Math.ceil(g.mesafeKm / 350) + 2; }
  karbon(g) { return g.mesafeKm * 95; }
}
EOF
cat > nesne-b/nokta.mjs <<'EOF'
export class Nokta {
  ucret(g) { return g.temelUcret - 750; }
  sure(g) { return Math.ceil(g.mesafeKm / 350) + 3; }
  karbon(g) { return g.mesafeKm * 60; }
}
EOF
# Fonksiyon modeli: yeni islem tek bir yeni dosyadir.
cat > fonk-b/karbon.mjs <<'EOF'
export function karbon(g) {
  switch (g.tasiyici) {
    case "hizli": return g.mesafeKm * 210;
    case "ekonomik": return g.mesafeKm * 95;
    case "nokta": return g.mesafeKm * 60;
    default: throw new Error(`bilinmeyen tasiyici: ${g.tasiyici}`);
  }
}
EOF
cat > karbon-kullan.mjs <<'EOF'
import { GONDERILER } from "./gonderiler.mjs";
import { TASIYICILAR } from "./nesne-b/kayit.mjs";
import { karbon } from "./fonk-b/karbon.mjs";

for (const g of GONDERILER) {
  console.log(`${g.kod} nesne ${TASIYICILAR[g.tasiyici].karbon(g)} fonk ${karbon(g)}`);
}
EOF
node karbon-kullan.mjs
for m in nesne fonk; do
  rapor=$(diff -rq -x 'kullan*' "$m" "$m-b")
  printf '%-6s yeni dosya: %d  degisen dosya: %d\n' "$m" \
    "$(printf '%s\n' "$rapor" | grep -c '^Only in')" \
    "$(printf '%s\n' "$rapor" | grep -c '^Files')"
done
```

```
TR-4471 nesne 94500 fonk 94500
TR-4472 nesne 114000 fonk 114000
TR-4473 nesne 19200 fonk 19200
nesne  yeni dosya: 0  degisen dosya: 3
fonk   yeni dosya: 1  degisen dosya: 0
```

İki model de doğru sonucu verir, ama maliyetleri eşit değildir. Fonksiyon modelinde yeni
işlem tek bir yeni dosyadır; var olan hiçbir dosya açılmamıştır. Nesne modelinde üç tür
dosyasının üçü de değişmiştir — yeni bir davranış, o davranışı taşıyacak her sınıfa ayrı ayrı
eklenmek zorundadır.

#### Yeni Bir Taşıyıcı Türü Eklendiğinde

İkinci değişiklik ters yöndedir: dördüncü bir taşıyıcı, gece kuryesi, eklenecek. Ücreti sabit
bir ek taşır, teslim süresi taşıdığı mesafeden bağımsız olarak bir gündür.

```sh
cp -r nesne nesne-a && cp -r fonk fonk-a
cat > gonderiler.mjs <<'EOF'
export const GONDERILER = [
  { kod: "TR-4471", temelUcret: 4990, mesafeKm: 450, tasiyici: "hizli" },
  { kod: "TR-4472", temelUcret: 6567, mesafeKm: 1200, tasiyici: "ekonomik" },
  { kod: "TR-4473", temelUcret: 14715, mesafeKm: 320, tasiyici: "nokta" },
  { kod: "TR-4474", temelUcret: 5739, mesafeKm: 280, tasiyici: "gece" },
];
EOF
# Nesne modeli: yeni tur bir yeni dosya, kayit dosyasi guncellenir.
cat > nesne-a/gece.mjs <<'EOF'
export class Gece {
  ucret(g) { return g.temelUcret + 4200; }
  sure(g) { return 1; }
}
EOF
cat > nesne-a/kayit.mjs <<'EOF'
import { Hizli } from "./hizli.mjs";
import { Ekonomik } from "./ekonomik.mjs";
import { Nokta } from "./nokta.mjs";
import { Gece } from "./gece.mjs";

export const TASIYICILAR = {
  hizli: new Hizli(), ekonomik: new Ekonomik(), nokta: new Nokta(), gece: new Gece(),
};
EOF
# Fonksiyon modeli: her islem dosyasina bir dal, veri dosyasina bir ad eklenir.
cat > fonk-a/veri.mjs <<'EOF'
export const TASIYICILAR = ["hizli", "ekonomik", "nokta", "gece"];
EOF
cat > fonk-a/ucret.mjs <<'EOF'
export function ucret(g) {
  switch (g.tasiyici) {
    case "hizli": return g.temelUcret + 2500;
    case "ekonomik": return g.temelUcret;
    case "nokta": return g.temelUcret - 750;
    case "gece": return g.temelUcret + 4200;
    default: throw new Error(`bilinmeyen tasiyici: ${g.tasiyici}`);
  }
}
EOF
cat > fonk-a/sure.mjs <<'EOF'
export function sure(g) {
  switch (g.tasiyici) {
    case "hizli": return Math.ceil(g.mesafeKm / 600) + 1;
    case "ekonomik": return Math.ceil(g.mesafeKm / 350) + 2;
    case "nokta": return Math.ceil(g.mesafeKm / 350) + 3;
    case "gece": return 1;
    default: throw new Error(`bilinmeyen tasiyici: ${g.tasiyici}`);
  }
}
EOF
node nesne-a/kullan.mjs | tail -n 1
node fonk-a/kullan.mjs | tail -n 1
for m in nesne fonk; do
  rapor=$(diff -rq -x 'kullan*' "$m" "$m-a")
  printf '%-6s yeni dosya: %d  degisen dosya: %d\n' "$m" \
    "$(printf '%s\n' "$rapor" | grep -c '^Only in')" \
    "$(printf '%s\n' "$rapor" | grep -c '^Files')"
done
```

```
TR-4474 9939 kurus 1 gun
TR-4474 9939 kurus 1 gun
nesne  yeni dosya: 1  degisen dosya: 1
fonk   yeni dosya: 0  degisen dosya: 3
```

Sayılar tersine dönmüştür. Nesne modelinde yeni tür bir yeni dosyadır; var olan üç tür
dosyasının hiçbiri açılmamış, yalnızca kayıt dosyası güncellenmiştir. Fonksiyon modelinde üç
dosyanın üçü de değişmiştir — her işlem, yeni türü tanımak için kendi dallanmasına bir dal
eklemek zorundadır.

İki ölçüm birlikte okunduğunda tablo tamamlanır. Nesne modeli **yeni türe** kapalıdır ama
yeni işleme açık değildir; fonksiyon modeli **yeni işleme** kapalıdır ama yeni türe açık
değildir. Bir modelin kapalı olduğu eksen, diğerinin açık olduğu eksendir. Bu simetriye
**ifade problemi (expression problem)** denir ve iki paradigmanın ödünleşimini tek cümlede
toplar.

#### Seçimin Ölçütü ve Karışım

Ölçüt bu yüzden bir üstünlük yargısı değil, bir tahmindir: sistemin hangi ekseninde büyüme
beklenmektedir. Taşıyıcı sayısı sabit, üzerinde yapılan işlem sayısı artan bir alanda —
ücret, süre, karbon, sigorta, iade — fonksiyon modeli her yeni işlemi tek dosyayla karşılar.
Taşıyıcı sayısı sürekli artan, işlem kümesi oturmuş bir alanda nesne modeli her yeni türü tek
dosyayla karşılar.

İki eksenin de büyüdüğü durumda seçim ikisinden birini almak değil, sınırı çizmektir. Bu
kursun ücretlendirme kitaplığı bu karışımın örneğidir. Ücret hesabının kendisi — kademe
seçimi, bölge çarpanı, indirimler, taban — saf fonksiyonlardan kurulmuş bir boru hattıdır ve
bir sonuç döndürür; girdi/çıktı ile zaman kabukta durur. Taşıyıcı seçenekleri ise nesnedir,
çünkü orada büyüyen eksen tür eksenidir. İki model aynı sistemde, birbirinin yerine değil,
farklı eksenlerde çalışır.

Karışımın tek kuralı sınırın açık olmasıdır. Nesnelerin yaşadığı yerde durum değişebilir;
fonksiyonların yaşadığı yerde veri değiştirilemezdir. Bir değerin bu sınırı hangi yönde
geçtiği belirsizse, iki modelin de güvencesi kaybolur.

#### Özet

- Aynı problem iki eksende bölünebilir: nesne modeli türe göre, fonksiyon modeli işleme göre.
  İki model de aynı sayıları üretir.
- Yeni bir işlem eklendiğinde fonksiyon modelinde 1 yeni dosya açılır ve hiçbir dosya
  değişmez; nesne modelinde üç tür dosyasının üçü de değişir.
- Yeni bir tür eklendiğinde nesne modelinde 1 yeni dosya ile 1 kayıt dosyası değişir;
  fonksiyon modelinde üç işlem dosyasının üçü de değişir.
- Bir modelin kapalı olduğu eksen diğerinin açık olduğu eksendir; bu simetri ifade problemi
  olarak adlandırılır.
- Seçim, sistemin hangi ekseninde büyüme beklendiğine bağlıdır; iki eksen de büyüyorsa doğru
  karar birini seçmek değil, iki modelin arasındaki sınırı çizmektir.

#### Kurs Kapanışı

Bu kurs bir programın hangi birimlere bölüneceği sorusunu iki ayrı yanıtla ele aldı.

İlk konu yapısal ve nesneye dayalı düzenlemeyi kurdu: denetim akışının disipline edilmesi,
durumun kapsüllenerek değişmezlerin korunması, gerekli olanı gösterip gerisini gizleyen
soyutlama, alt tip ilişkisi olarak kalıtım, üç ayrı çok biçimlilik biçimi, arayüz ile soyut
sınıf arasındaki seçim, kırılgan üst sınıf karşısında bileşimin tercih edilmesi ve veri ile
davranışın birlikte tutulması. İkinci konu fonksiyonel modeli kurdu: saf fonksiyonlar ve
referans saydamlığı, değiştirilemez veriyle paylaşılan durumun ortadan kaldırılması,
davranışın fonksiyon parametresiyle dışarı alınması, küçük adımlardan kurulan boru hatları,
yan etkilerin saf çekirdek ile kirli kabuk arasında bölünmesi ve son olarak iki modelin aynı
sistemde ölçüte bağlı seçimi.

İkisinin ortak bıraktığı bir varsayım vardır. Her iki paradigma da **tek tek birimlerin**
nasıl kurulacağını anlattı — birim nesne mi olacak, fonksiyon mu; içinde ne duracak, dışında
ne kalacak. Kurulan ölçütlerin hepsi birimin içine bakar: kaç karar noktası var, kaç satır
dışarıya dokunuyor, kaç sahte bağımlılık gerekiyor. Birimler arasındaki ilişkinin ne zaman
sağlıklı olduğu ise ölçülmedi. Oysa bir tasarımın iyi olup olmadığı çoğu zaman birimin içine
değil, birimler arasındaki bağa bakılarak anlaşılır: bir birim kaç başkasını tanıyor, bir
değişiklik kaç birime yayılıyor, bir birimin içindeki parçalar gerçekten aynı işe mi ait.

Sonraki kurs, Tasarım İlkeleri, bu soruyu ele alır. SOLID ilkeleri birimlerin sorumluluğunu
ve genişletilme biçimini adlandırır; bağlaşım ve uyum ölçütleri birimler arasındaki bağı ve
birim içindeki bütünlüğü sayılabilir hâle getirir; bağımlılık yönünün soyutlamalara
çevrilmesi, hangi birimin hangisini tanıyacağını bir tercih olmaktan çıkarıp bir kurala
bağlar. Bu kursta kurulan birimler, orada birbirine bağlanacak.

