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

# Dekoratör

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

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

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

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

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

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

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

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

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

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

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

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

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

```
uretilen sinif = 16
```

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Beşinci Ek Geldiğinde

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Özet

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

## Sonraki Adım

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