---
title: Bileşik
source: 'https://academia.sh/tr/kurslar/tasarim-kaliplari/bilesik'
course: 'Tasarım Kalıpları'
language: tr
updated: '2026-08-17T18:10:39+00:00'
license: 'CC BY-SA 4.0'
---

# Bileşik

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

const DUGUM = { konsolide: Konsolide };

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Yeni Bir İşlem Eklenince

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

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

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

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

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

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

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

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

## Bedel ve Ne Zaman Uygulanmayacağı

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

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

## Özet

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

## Sonraki Adım

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