---
title: Soyutlama
source: 'https://academia.sh/tr/kurslar/paradigmalar/soyutlama'
course: 'Programlama Paradigmaları'
language: tr
updated: '2026-08-17T18:10:41+00:00'
license: 'CC BY-SA 4.0'
---

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