Ders 07 / 30
Bağdaştırıcı
Uyumsuz arayüzlerin köprülenmesi: iki dış taşıyıcı sağlayıcısının sözlüğüne dokunan dosyaların çevirinin istemciye dağıldığı düzende ve bağdaştırıcı arkasındaki düzende sayılması, üçüncü sağlayıcı eklendiğinde düzenlenen dosya ile eklenen satırın karşılaştırılması ve kalıbın dosya, satır ve dolaylılık cinsinden bedelinin çıkarılması.
İçindekiler
Yaratımsal kalıplar bir nesnenin kim tarafından ve nasıl kurulacağı sorusunu çözdü: fabrika yöntemi tür seçimini bir yere topladı, kurucu çok adımlı kurulumu sıraya soktu, bağımlılık enjeksiyonu yaratma sorumluluğunu nesneyi kullanan koddan aldı. Hepsinde kurulan şey kitaplığın kendi nesnesiydi ve arayüzünü de kitaplık yazıyordu.
Bu konu nesnelerin nasıl birleştirileceğini düzenler. İlk problem, şeklini kitaplığın belirlemediği bir arayüzle çalışmaktır: gönderi ücretlendirme kitaplığı kaç taşıyıcıyla çalışacaksa o kadar dış sağlayıcıya bağlanır, her sağlayıcı kendi sözlüğünü, kendi birimini ve “bu gönderiyi taşımıyorum” demenin kendi biçimini getirir. Bağdaştırıcı (adapter) bu uyumsuzluğu tek bir yerde çözer. Kazancı iki sayıyla ölçülür: sağlayıcı sözlüğüne dokunan dosya sayısı ve üçüncü bir sağlayıcı eklendiğinde düzenlenen var olan dosya sayısı.
Problem: İki Sözlük, İki Hata Biçimi
İki dış sağlayıcı aynı işi yapar ve hiçbir noktada uyuşmaz. Birincisi ağırlığı gram, ücreti
kuruş olarak alır ve kademe dışında null döndürür.
// saglayici/kara.mjs — dis saglayici: gram ve kurus ile calisir, kademe disinda null doner const KADEME = [[1000, 4990], [5000, 8490], [15000, 14990], [30000, 24990]]; const KATSAYI = { 34: 100, "06": 115, 35: 120, 65: 145 }; export function bedelHesapla({ gramaj, hedefKod }) { const kademe = KADEME.find(([ust]) => gramaj <= ust); if (kademe === undefined) return null; const katsayi = KATSAYI[hedefKod.slice(0, 2)] ?? 165; return { kurusBedel: Math.round((kademe[1] * katsayi) / 100), gunAdedi: katsayi > 130 ? 4 : 2 }; }
İkincisi kilogram ve lira ile çalışır, posta kodu yerine bölge adı bekler ve taşımadığı gönderide hata atar.
// saglayici/hava.mjs — dis saglayici: kilogram ve lira ile calisir, kapsam disinda hata atar const BOLGE_TABAN = { yakin: 39.9, orta: 64.5, uzak: 98 }; export function fiyatSor({ kiloAgirlik, bolgeAdi }) { const taban = BOLGE_TABAN[bolgeAdi]; if (taban === undefined) throw new RangeError(`kapsam disi bolge: ${bolgeAdi}`); if (kiloAgirlik > 20) throw new RangeError("azami agirlik 20 kg"); return { lira: Math.round((taban + kiloAgirlik * 8.4) * 100) / 100, aktarmaGunu: bolgeAdi === "uzak" ? 3 : 1, }; }
Kitaplığın kendi sözleşmesi tek: bir teklif { tasiyici, tutar, gun } nesnesidir, tutar
kuruştur, taşınamayan gönderi için teklif null’dır. Bölge tablosu ve örnek gönderiler
kitaplığın kendi malıdır; iki düzen de bunları paylaşır.
// bolge.mjs — kitapligin kendi bolge tablosu ve ornek gonderiler const POSTA_BOLGE = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" }; export const bolgeBul = (postaKodu) => POSTA_BOLGE[postaKodu.slice(0, 2)] ?? "bilinmeyen"; export const ORNEK = [ { kod: "G-1", agirlik: 0.8, postaKodu: "34100" }, { kod: "G-2", agirlik: 12, postaKodu: "06500" }, { kod: "G-3", agirlik: 26, postaKodu: "65100" }, { kod: "G-4", agirlik: 3, postaKodu: "48200" }, { kod: "G-5", agirlik: 8, postaKodu: "35400" }, ];
Çevirinin İstemciye Dağıldığı Düzen
İlk düzende çeviriyi sağlayıcıyı çağıran her yer kendisi yapar. Teklifleri sıralayan modül gram çevirisini, lira çevirisini ve iki ayrı hata biçimini kendi gövdesinde taşır.
// dogrudan/secim.mjs — secim iki saglayicinin sozlugunu ve birimini kendi icinde cevirir import { bedelHesapla } from "../saglayici/kara.mjs"; import { fiyatSor } from "../saglayici/hava.mjs"; import { bolgeBul } from "../bolge.mjs"; export function teklifler(gonderi) { const cikan = []; const k = bedelHesapla({ gramaj: Math.round(gonderi.agirlik * 1000), hedefKod: gonderi.postaKodu }); if (k !== null) cikan.push({ tasiyici: "kara", tutar: k.kurusBedel, gun: k.gunAdedi }); try { const h = fiyatSor({ kiloAgirlik: gonderi.agirlik, bolgeAdi: bolgeBul(gonderi.postaKodu) }); cikan.push({ tasiyici: "hava", tutar: Math.round(h.lira * 100), gun: h.aktarmaGunu }); } catch (e) { if (!(e instanceof RangeError)) throw e; } return cikan.sort((a, b) => a.tutar - b.tutar); }
İkinci bir soru — bir gönderiyi kaç taşıyıcının aldığı — aynı çevirinin ikinci kopyasını doğurur. Kopyalanan şey birim değil, “taşımıyorum” ifadesinin iki sağlayıcıda iki farklı biçim aldığı bilgisidir.
// dogrudan/kapsam.mjs — ayni cevirinin ikinci kopyasi: kac saglayici tasiyabiliyor import { bedelHesapla } from "../saglayici/kara.mjs"; import { fiyatSor } from "../saglayici/hava.mjs"; import { bolgeBul } from "../bolge.mjs"; export function tasiyanSayisi(gonderi) { let sayi = 0; if (bedelHesapla({ gramaj: Math.round(gonderi.agirlik * 1000), hedefKod: gonderi.postaKodu }) !== null) sayi += 1; try { fiyatSor({ kiloAgirlik: gonderi.agirlik, bolgeAdi: bolgeBul(gonderi.postaKodu) }); sayi += 1; } catch (e) { if (!(e instanceof RangeError)) throw e; } return sayi; }
Çözüm: Sağlayıcı Başına Bir Bağdaştırıcı
Kalıbın çözümü, her sağlayıcının önüne kitaplığın sözleşmesini karşılayan bir nesne koymaktır. Bağdaştırıcı üç şeyi çevirir: alan adlarını, birimleri ve hata biçimini.
// bagdastirici/kara-bagdastirici.mjs — gram/kurus sozlugunu kitapligin teklif sozlesmesine cevirir import { bedelHesapla } from "../saglayici/kara.mjs"; export const karaBagdastirici = { teklif(gonderi) { const k = bedelHesapla({ gramaj: Math.round(gonderi.agirlik * 1000), hedefKod: gonderi.postaKodu }); return k === null ? null : { tasiyici: "kara", tutar: k.kurusBedel, gun: k.gunAdedi }; }, };
// bagdastirici/hava-bagdastirici.mjs — lira/bolge sozlugunu ve hatayi ayni sozlesmeye cevirir import { fiyatSor } from "../saglayici/hava.mjs"; import { bolgeBul } from "../bolge.mjs"; export const havaBagdastirici = { teklif(gonderi) { try { const h = fiyatSor({ kiloAgirlik: gonderi.agirlik, bolgeAdi: bolgeBul(gonderi.postaKodu) }); return { tasiyici: "hava", tutar: Math.round(h.lira * 100), gun: h.aktarmaGunu }; } catch (e) { if (e instanceof RangeError) return null; throw e; } }, };
Sağlayıcıyı çağıran iki modül artık tek sözleşme görür ve sağlayıcı adlarını hiç bilmez.
// bagdastirici/secim.mjs — saglayici sozlugunden hicbir ad gecmez export const teklifler = (gonderi, bagdastiricilar) => bagdastiricilar .map((b) => b.teklif(gonderi)) .filter((t) => t !== null) .sort((a, b) => a.tutar - b.tutar);
// bagdastirici/kapsam.mjs — tek sozlesme uzerinden sayar import { teklifler } from "./secim.mjs"; export const tasiyanSayisi = (gonderi, bagdastiricilar) => teklifler(gonderi, bagdastiricilar).length;
// bagdastirici/kur.mjs — bilesim koku: hangi bagdastiricilarin kullanildigini yalniz burasi bilir import { karaBagdastirici } from "./kara-bagdastirici.mjs"; import { havaBagdastirici } from "./hava-bagdastirici.mjs"; export const BAGDASTIRICILAR = [karaBagdastirici, havaBagdastirici];
Karşılaştırmanın anlamı için iki düzenin aynı gönderilere aynı yanıtı vermesi gerekir.
// kosum.mjs — iki duzen ayni teklif sirasini ve ayni tasiyan sayisini veriyor mu import { ORNEK } from "./bolge.mjs"; import { teklifler as dogrudanTeklif } from "./dogrudan/secim.mjs"; import { tasiyanSayisi as dogrudanSayi } from "./dogrudan/kapsam.mjs"; import { teklifler as kopruluTeklif } from "./bagdastirici/secim.mjs"; import { tasiyanSayisi as kopruluSayi } from "./bagdastirici/kapsam.mjs"; import { BAGDASTIRICILAR } from "./bagdastirici/kur.mjs"; let ayrilan = 0; for (const g of ORNEK) { const a = `${JSON.stringify(dogrudanTeklif(g))} tasiyan=${dogrudanSayi(g)}`; const b = `${JSON.stringify(kopruluTeklif(g, BAGDASTIRICILAR))} tasiyan=${kopruluSayi(g, BAGDASTIRICILAR)}`; if (a !== b) ayrilan += 1; console.log(`${g.kod} ${a}`); } console.log(`ayrilan sonuc = ${ayrilan} / ${ORNEK.length}`);
G-1 [{"tasiyici":"hava","tutar":4662,"gun":1},{"tasiyici":"kara","tutar":4990,"gun":2}] tasiyan=2
G-2 [{"tasiyici":"hava","tutar":16530,"gun":1},{"tasiyici":"kara","tutar":17239,"gun":2}] tasiyan=2
G-3 [{"tasiyici":"kara","tutar":36236,"gun":4}] tasiyan=1
G-4 [{"tasiyici":"kara","tutar":14009,"gun":4}] tasiyan=1
G-5 [{"tasiyici":"hava","tutar":13170,"gun":1},{"tasiyici":"kara","tutar":17988,"gun":2}] tasiyan=2
ayrilan sonuc = 0 / 5
Beş gönderide aynı sıra, aynı tutar, aynı taşıyan sayısı. Kademe dışı ağırlık (G-3) ve tablosuz posta kodu (G-4) dâhil, iki düzen davranışta ayrılmıyor.
Bağlaşımın Ölçülmesi
Ölçü, Tasarım İlkeleri kursundaki Çerçeve Kodunu Uzakta Tutmak dersinin kullandığı ölçünün aynısıdır: dış sözlükten en az bir ad geçen dosya sayısı. Yanına ikinci bir sütun konuyor — birim çevirisinin kaç yerde tekrarlandığı.
// sozluk-say.mjs — saglayici sozlugunden ad gecen dosyalari ve birim cevirisini sayar import { readdirSync, readFileSync } from "node:fs"; const SOZLUK = ["bedelHesapla", "gramaj", "hedefKod", "kurusBedel", "gunAdedi", "fiyatSor", "kiloAgirlik", "bolgeAdi", "lira", "aktarmaGunu"]; const CEVRIM = [/\* 1000/g, /lira \* 100/g]; for (const dizin of ["dogrudan", "bagdastirici"]) { const dosyalar = readdirSync(dizin).filter((d) => d.endsWith(".mjs")).sort(); let dokunan = 0; console.log(`${dizin}/`); for (const d of dosyalar) { const metin = readFileSync(`${dizin}/${d}`, "utf8"); const ad = SOZLUK.filter((a) => new RegExp(`\\b${a}\\b`).test(metin)).length; const cevrim = CEVRIM.reduce((t, k) => t + (metin.match(k)?.length ?? 0), 0); if (ad > 0) dokunan += 1; console.log(` ${d.padEnd(24)} saglayici adi=${String(ad).padStart(2)} birim cevirisi=${cevrim}`); } console.log(` sozluge dokunan dosya = ${dokunan} / ${dosyalar.length}`); }
dogrudan/ kapsam.mjs saglayici adi= 6 birim cevirisi=1 secim.mjs saglayici adi=10 birim cevirisi=2 sozluge dokunan dosya = 2 / 2 bagdastirici/ hava-bagdastirici.mjs saglayici adi= 5 birim cevirisi=1 kapsam.mjs saglayici adi= 0 birim cevirisi=0 kara-bagdastirici.mjs saglayici adi= 5 birim cevirisi=1 kur.mjs saglayici adi= 0 birim cevirisi=0 secim.mjs saglayici adi= 0 birim cevirisi=0 sozluge dokunan dosya = 2 / 5
İlk düzende iki dosyanın ikisi de sözlüğe dokunuyor; ikincisinde beş dosyanın ikisi ve dokunan iki dosya bağdaştırıcıların kendisi. Seçim ile kapsam sayımı sıfır ada dokunuyor. İkinci sütun bağlaşımın türünü de gösteriyor: birim çevirisi ilk düzende üç yerde, ikincide iki yerde — ve o iki yer aynı sağlayıcının tek çeviri noktasıdır, aynı çevirinin kopyası değil.
Tasarım İlkeleri kursundaki o ders bağdaştırıcıyı bir yalıtım aracı olarak kullanmıştı: tek bir çerçevenin bağlam nesnesini kitaplığın düz istek biçimine çeviren tek dosya, denetim ters yönde. Burada durum farklıdır — sağlayıcı sayısı birden çoktur, denetim kitaplıktadır ve çevirinin amacı yalıtım değil aynı sözleşme altında karşılaştırılabilirliktir. Beş gönderinin tekliflerinin tek listede sıralanabilmesi bu ortak sözleşmenin sonucudur.
Üçüncü Sağlayıcı Eklenince
Sayılar bir değişiklik geldiğinde karşılığını verir. Üçüncü sağlayıcı ilk ikisine de benzemez:
konumsal argüman alır, dizi döndürür ve taşımadığında undefined verir. İki ağaç kopyalanıp
değişiklik ikisine de uygulanıyor.
mkdir -p yeni && cp -r saglayici bolge.mjs dogrudan bagdastirici kosum.mjs yeni/ ls yeni
bagdastirici bolge.mjs dogrudan kosum.mjs saglayici
// yeni/saglayici/deniz.mjs — ucuncu dis saglayici: konumsal arguman alir, dizi doner, tasimadiginda undefined const LIMAN = { 34: 1, 35: 1.1 }; export function navlun(agirlikKg, postaKodu) { const carpan = LIMAN[postaKodu.slice(0, 2)]; if (carpan === undefined) return undefined; return [Math.round((2600 + agirlikKg * 260) * carpan), 6]; }
// yeni/bagdastirici/deniz-bagdastirici.mjs — konumsal arguman ve dizi yanitini ayni sozlesmeye cevirir import { navlun } from "../saglayici/deniz.mjs"; export const denizBagdastirici = { teklif(gonderi) { const d = navlun(gonderi.agirlik, gonderi.postaKodu); return d === undefined ? null : { tasiyici: "deniz", tutar: d[0], gun: d[1] }; }, };
Bağdaştırıcılı düzende yapılacak tek düzenleme bileşim kökündeki listedir. Doğrudan düzende
ise iki dosyanın gövdesine üçüncü hata biçimi girer. Aşağıdaki betik iki düzenlemeyi de
uygular, iki ağacı koşturur ve farkı sayar; yerinde düzenlemede yedek uzantısı verildiği için
GNU ve BSD sed aynı davranır.
cd yeni sed -i.y -e 's#^import { bolgeBul } from "../bolge.mjs";#&\nimport { navlun } from "../saglayici/deniz.mjs";#' dogrudan/secim.mjs dogrudan/kapsam.mjs sed -i.y 's#^ return cikan.sort# const d = navlun(gonderi.agirlik, gonderi.postaKodu);\n if (d !== undefined) cikan.push({ tasiyici: "deniz", tutar: d[0], gun: d[1] });\n&#' dogrudan/secim.mjs sed -i.y 's#^ return sayi;# if (navlun(gonderi.agirlik, gonderi.postaKodu) !== undefined) sayi += 1;\n&#' dogrudan/kapsam.mjs sed -i.y -e 's#^import { havaBagdastirici } from "./hava-bagdastirici.mjs";#&\nimport { denizBagdastirici } from "./deniz-bagdastirici.mjs";#' \ -e 's#havaBagdastirici\];#havaBagdastirici, denizBagdastirici];#' bagdastirici/kur.mjs rm -f dogrudan/*.y bagdastirici/*.y node kosum.mjs | head -2 for d in dogrudan bagdastirici; do echo "$d" echo " duzenlenen var olan dosya = $(diff -rq "../$d" "$d" | grep -c '^Files ')" echo " yeni dosya = $(diff -rq "../$d" "$d" | grep -c '^Only in ')" echo " eklenen satir = $(diff -rN "../$d" "$d" | grep '^>' | grep -cvE '^> *(//|$)')" done
G-1 [{"tasiyici":"deniz","tutar":2808,"gun":6},{"tasiyici":"hava","tutar":4662,"gun":1},{"tasiyici":"kara","tutar":4990,"gun":2}] tasiyan=3
G-2 [{"tasiyici":"hava","tutar":16530,"gun":1},{"tasiyici":"kara","tutar":17239,"gun":2}] tasiyan=2
dogrudan
duzenlenen var olan dosya = 2
yeni dosya = 0
eklenen satir = 5
bagdastirici
duzenlenen var olan dosya = 1
yeni dosya = 1
eklenen satir = 9
Doğrudan düzende iki var olan dosya düzenlendi, bağdaştırıcılı düzende bir — ve o bir dosya bileşim köküdür, iş kuralı taşımaz. Bağdaştırıcılı düzen daha çok satır ekledi (9’a 5), çünkü yeni sağlayıcının çevirisi kendi dosyasında tam bir nesne olarak duruyor. Açık–Kapalı İlkesi dersindeki ayrım burada da geçerlidir: eklenen satır yeniden doğrulanmayı gerektirmez, düzenlenen satır gerektirir. Doğrudan düzende düzenlenen iki gövde, daha önce doğru çalışan iki sağlayıcının hata yollarını da içeriyordu.
Bedel ve Ne Zaman Uygulanmayacağı
Kalıbın bedeli aynı ölçekte sayılabilir.
for d in dogrudan bagdastirici; do echo "$d: $(ls $d/*.mjs | wc -l | tr -d ' ') dosya, $(cat $d/*.mjs | grep -cvE '^ *(//)?$') satir" done
dogrudan: 2 dosya, 31 satir bagdastirici: 5 dosya, 35 satir
Üç dosya ve dört satır fazladan; buna bir de dolaylılık basamağı eklenir. Doğrudan düzende
bir teklifin izlenmesi için iki dosya okunur (seçim ve sağlayıcı), bağdaştırıcılı düzende üç
(seçim, bağdaştırıcı, sağlayıcı). Bağdaştırıcı sağlayıcı listesi enjekte edildiği için ithal
grafiğinde de görünmez: bagdastirici/secim.mjs hiçbir sağlayıcıyı ithal etmez, bağ çalışma
zamanında bileşim kökünde kurulur. Hata ayıklarken izlenecek çağrı sayısı artar.
Kalıp üç durumda karşılığını vermez. Sağlayıcı tek ve sözleşmesi kitaplığın denetimindeyse çevirinin ayrı dosyaya çıkarılması yalnız bir dosya ekler. Sağlayıcının sözlüğü kitaplığın sözlüğüyle zaten örtüşüyorsa bağdaştırıcı gövdesi alan adlarını birebir kopyalayan bir katmana düşer. Üçüncüsü daha inceden ısırır: bağdaştırıcı yalnız eşlediği kadarını geçirir. Bir sağlayıcının sunduğu ama sözleşmede karşılığı olmayan yetenek — parçalı gönderi, teslim saati aralığı — iş kuralı tarafından kullanılamaz; kullanılacaksa önce ortak sözleşmenin büyütülmesi ve bütün bağdaştırıcıların o alana bir yanıt vermesi gerekir. Ortak sözleşmenin maliyeti sağlayıcı sayısıyla doğru orantılı büyür.
Özet
- Bağdaştırıcının çözdüğü problem, kitaplığın belirlemediği bir arayüzle kitaplığın kendi sözleşmesi arasındaki uyumsuzluktur; çözüm, çeviriyi sağlayıcı başına tek bir nesnede toplamaktır.
- Çeviri üç şeyi kapsar: alan adları, birimler ve “taşımıyorum” ifadesinin biçimi — sonuncusu
nullile hata arasındaki fark olduğu için en çok kopyalanan bilgidir. - Dış sözlüğe dokunan dosya sayısı doğrudan düzende 2/2, bağdaştırıcılı düzende 2/5’tir ve dokunan iki dosya bağdaştırıcıların kendisidir; birim çevirisi 3 yerden 2 yere iner.
- Üçüncü sağlayıcı eklenince doğrudan düzende 2 var olan dosya düzenlendi, bağdaştırıcılı düzende 1 (bileşim kökü) düzenlendi ve 1 yeni dosya eklendi; beş gönderinin yanıtları iki düzende de aynı kaldı.
- Bedel 3 dosya, 4 satır ve bir dolaylılık basamağıdır; tek sağlayıcılı, sözlüğü örtüşen ya da yeteneklerinin ortak sözleşmeye sığmadığı durumlarda bu bedel karşılıksız kalır.
Sonraki Adım
Bağdaştırıcı var olan bir arayüzü sonradan uydurdu: sağlayıcılar zaten yazılmıştı, kitaplığın sözleşmesi de. Kalıp ikisinin arasına girdi. Bir sonraki problem baştan tasarım aşamasında çıkar. Kitaplık ücret raporu üretecekse iki bağımsız eksen vardır: raporun türü (özet, kademe dökümü, bölge dökümü) ve raporun biçimi (düz metin, ayrılmış değer, hizalanmış tablo). İki eksen kalıtımla birleştirilirse tür sayısı ile biçim sayısının çarpımı kadar sınıf doğar. Sonraki ders bu çarpımı üç tür ve iki biçim için sayar, iki ekseni ayrı hiyerarşilere bölen köprü kalıbını yazar ve üçüncü bir biçim eklendiğinde iki düzende eklenen tip ile eklenen satırı karşılaştırır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.