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

# Köprü

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Üçüncü Biçim Eklenince

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Bedel ve Ne Zaman Uygulanmayacağı

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

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

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

## Özet

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

## Sonraki Adım

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