Ders 06 / 30
Bağımlılık Enjeksiyonu
Yaratım sorumluluğunun kullanan koddan alınıp bileşim köküne taşınması: bağımlılıklarını kendisi kuran ücret modülü ile bağımlılıklarını çağrıda alan sürümün doğrudan bağ sayısı ve ithal kapanışı boyutuyla karşılaştırılması, indirim politikası değiştiğinde düzenlenen dosyanın sayılması ve kapanışın yok olmayıp yer değiştirdiğinin gösterilmesi.
İçindekiler
Bir önceki ders kayıt için örnek üreten sürümü sınamalardan geçirdi, ama kaydı kullanan modülün
o örneği nereden alacağını söylemedi. Modül yeniKayit çağrısını kendi gövdesinde yaparsa her
modül kendi kaydını kurar; modül tekil sürümü ithal ederse küresel durum geri gelir. Üçüncü
seçenek nesneyi hiç kurmamaktır.
Bağımlılık enjeksiyonu Frontend Mimarisi ve Birim Testi kurslarında tanımlanmış bir düzenektir; burada yeniden tanımlanmaz. Bu dersin sorduğu şey daha dar: bir modülün bağımlılığını kendisi kurması ile dışarıdan alması arasındaki fark hangi sayılarla görünür. Ölçüler Tasarım İlkeleri kursundan gelir: bir modülün doğrudan bağ sayısı ve ithal kapanışı boyutu.
Sağlayıcı Modüller
İki sürüm de aynı üç parçayı kullanır: tarife nesneleri, onları tutan bir kayıt ve bir indirim politikası. Bu parçalar iki sürüm için ortaktır.
// saglayici/tarife.mjs — iki tarife nesnesi export const STANDART = { ad: "standart", taban: (g) => 3900 + Math.ceil(g.agirlik) * 900 }; export const EKSPRES = { ad: "ekspres", taban: (g) => 6400 + Math.ceil(g.agirlik) * 1500 };
// saglayici/kayit.mjs — tarife kaydi; iki tarife yuklenmis bir ornek uretir import { STANDART, EKSPRES } from "./tarife.mjs"; export class TarifeKaydi { #tarifeler = new Map(); kaydet(tarifeNesnesi) { this.#tarifeler.set(tarifeNesnesi.ad, tarifeNesnesi); return this; } tarife(ad) { const t = this.#tarifeler.get(ad); if (t === undefined) throw new RangeError(`bilinmeyen tarife: ${ad}`); return t; } } export const yeniKayit = () => new TarifeKaydi().kaydet(STANDART).kaydet(EKSPRES);
// saglayici/indirim.mjs — iki indirim politikasi export const sozlesmeliIndirim = (gonderi, ham) => gonderi.sozlesmeNo === null ? 0 : Math.round(ham * 0.12); export const indirimsiz = () => 0;
Bağımlılığını Kendisi Kuran Modül
Birinci sürümde ücret modülü ihtiyacı olan her şeyi kendisi ithal eder ve kaydı kendisi kurar.
Çağıran taraf yalnız ucret işlevini görür.
// kendi/ucret.mjs — bagimliliklarini kendisi ithal eder ve kendisi kurar import { yeniKayit } from "../saglayici/kayit.mjs"; import { sozlesmeliIndirim } from "../saglayici/indirim.mjs"; const BOLGE = { "34": 100, "06": 115, "65": 140 }; const KAYIT = yeniKayit(); export function ucret(gonderi) { const taban = KAYIT.tarife(gonderi.tarife).taban(gonderi); const ham = Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 160)) / 100); return ham - sozlesmeliIndirim(gonderi, ham); }
// kendi/kur.mjs — cagiran yalniz ucret islevini ithal eder import { ucret } from "./ucret.mjs"; export const ORNEK = [ { tarife: "standart", agirlik: 2.4, adres: "34100", sozlesmeNo: "S1042" }, { tarife: "ekspres", agirlik: 2.4, adres: "06500", sozlesmeNo: null }, ]; for (const g of ORNEK) console.log(`kendi ${g.tarife.padEnd(9)} ${ucret(g)} kurus`);
node kendi/kur.mjs
kendi standart 5808 kurus kendi ekspres 12535 kurus
Bu sürümün çağrı yeri en kısa olanıdır: tek bir ithal ve tek bir çağrı. Bedeli çağrı yerinde değil, ücret modülünün kime bağlandığında.
Bağımlılığını Çağrıda Alan Modül
İkinci sürümde ücret modülü hiçbir şey ithal etmez. Kayıt ve indirim politikası çağrıda verilir; modül bir hesap işlevi üretir.
// enjekte/ucret.mjs — bagimliliklari cagrida alir; hicbir modul ithal etmez const BOLGE = { "34": 100, "06": 115, "65": 140 }; export const ucretHesabiYap = ({ kayit, indirim }) => (gonderi) => { const taban = kayit.tarife(gonderi.tarife).taban(gonderi); const ham = Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 160)) / 100); return ham - indirim(gonderi, ham); };
Kurulum işi bileşim köküne geçer. Kök parçaları kurar, hangi indirim politikasının kullanılacağını seçer ve hesabı üretir.
// enjekte/kur.mjs — bilesim koku: parcalari kurar, secer ve enjekte eder import { yeniKayit } from "../saglayici/kayit.mjs"; import { sozlesmeliIndirim, indirimsiz } from "../saglayici/indirim.mjs"; import { ucretHesabiYap } from "./ucret.mjs"; const POLITIKA = { sozlesmeli: sozlesmeliIndirim, yok: indirimsiz }; const secim = process.argv[2] ?? "sozlesmeli"; const ucret = ucretHesabiYap({ kayit: yeniKayit(), indirim: POLITIKA[secim] }); export const ORNEK = [ { tarife: "standart", agirlik: 2.4, adres: "34100", sozlesmeNo: "S1042" }, { tarife: "ekspres", agirlik: 2.4, adres: "06500", sozlesmeNo: null }, ]; for (const g of ORNEK) console.log(`enjekte ${secim.padEnd(11)} ${g.tarife.padEnd(9)} ${ucret(g)} kurus`);
node enjekte/kur.mjs node enjekte/kur.mjs yok
enjekte sozlesmeli standart 5808 kurus enjekte sozlesmeli ekspres 12535 kurus enjekte yok standart 6600 kurus enjekte yok ekspres 12535 kurus
İlk iki satır birinci sürümün çıktısıyla birebir aynı. Son iki satır indirim politikası değiştirildiğinde ne olduğunu gösteriyor: sözleşmeli gönderinin ücreti 5808 kuruştan 6600 kuruşa çıktı, sözleşmesiz gönderinin ücreti değişmedi. İki koşum arasında hiçbir dosya düzenlenmedi.
Bağların ve Kapanışın Sayılması
Aşağıdaki ölçer verilen modüllerin doğrudan ithal ettiği modül sayısını ve ithal kapanışının boyutunu verir. Kapanış, modülün ulaştığı bütün modüllerin geçişli kümesidir.
// kapanis.mjs — verilen modullerin dogrudan bag sayisi ve ithal kapanisi boyutu import { readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; const ITHAL = /(?:from|import)\s*["'](\.[^"']+)["']/g; const baglar = (yol) => [...readFileSync(yol, "utf8").matchAll(ITHAL)].map((m) => m[1]); function kapanis(yol, gorulen = new Set()) { for (const bag of baglar(yol)) { const hedef = resolve(dirname(yol), bag); if (!gorulen.has(hedef)) { gorulen.add(hedef); kapanis(hedef, gorulen); } } return gorulen; } for (const yol of process.argv.slice(2)) console.log(`${yol.padEnd(18)} dogrudan-bag=${baglar(yol).length} ` + `ithal-kapanisi=${kapanis(yol).size}`);
node kapanis.mjs kendi/ucret.mjs enjekte/ucret.mjs kendi/kur.mjs enjekte/kur.mjs
kendi/ucret.mjs dogrudan-bag=2 ithal-kapanisi=3 enjekte/ucret.mjs dogrudan-bag=0 ithal-kapanisi=0 kendi/kur.mjs dogrudan-bag=1 ithal-kapanisi=4 enjekte/kur.mjs dogrudan-bag=3 ithal-kapanisi=4
İlk iki satır kazancı veriyor. Ücret modülünün doğrudan bağ sayısı 2’den 0’a, ithal kapanışı boyutu 3’ten 0’a indi. Sıfır kapanış, modülün metninde hiçbir somut ayrıntının adının geçmediği anlamına gelir: kayıt sınıfının adı, tarife nesnelerinin adı ve indirim işlevinin adı ücret modülünde yoktur.
Son iki satır kalıbın gerçekte ne yaptığını söylüyor. İki sürümün toplam kapanışı da 4’tür. Yani kapanış yok olmadı, yer değiştirdi: birinci sürümde ücret modülünün üstündeydi, ikinci sürümde bileşim kökünün. Bileşim kökünün doğrudan bağ sayısı 1’den 3’e çıktı. Yaratım sorumluluğunun taşınması ölçüde tam olarak böyle görünür — bir modülün kazandığı bağımsızlık başka bir modülün kazandığı bağ sayısıdır.
Politika Değiştiğinde Düzenlenen Dosya
İkinci ölçü şu istekle çıkar: indirim politikası kaldırılacak. Aşağıdaki betik iki ağacı
kopyalar, isteği ikisine de uygular, sonuçları koşturur ve düzenlenen dosyaları sayar. Yerinde
düzenlemede yedek uzantısı verilmiştir; GNU ve BSD sed bu biçimde aynı davranır.
cp -r kendi kendi-yok cp -r enjekte enjekte-yok sed -i.y 's/sozlesmeliIndirim/indirimsiz/g' kendi-yok/ucret.mjs rm -f kendi-yok/*.y node kendi-yok/kur.mjs node enjekte-yok/kur.mjs yok for p in kendi enjekte; do echo "$p duzenlenen dosya = $(diff -rq $p $p-yok | grep -c '^Files ')" echo "$p ucret modulu degisti mi = $(diff -q $p/ucret.mjs $p-yok/ucret.mjs > /dev/null && echo hayir || echo evet)" done
kendi standart 6600 kurus kendi ekspres 12535 kurus enjekte yok standart 6600 kurus enjekte yok ekspres 12535 kurus kendi duzenlenen dosya = 1 kendi ucret modulu degisti mi = evet enjekte duzenlenen dosya = 0 enjekte ucret modulu degisti mi = hayir
İlk dört satır iki ağacın aynı ücretleri ürettiğini gösteriyor; ilk iki satırdaki kendi
etiketi modül metninde sabit olduğu için kopyada da öyle kalmıştır. Son dört satır ölçüyü
veriyor: birinci sürümde istek karşılanırken bir dosya düzenlendi ve o dosya ücret modülünün
kendisiydi. İkinci sürümde hiçbir dosya düzenlenmedi; politika seçimi zaten bileşim kökünün
dışında, çalışma anında verilen bir değerdi.
Bu, Açık–Kapalı İlkesi dersindeki ayrımın aynısıdır ve son satır belirleyici olandır: ücret hesabını yapan modül birinci sürümde değişti, ikincisinde değişmedi.
Bedel
Kalıbın bedeli üç yerde. Birincisi çağrı yerinin uzunluğu: ücret hesabını elde etmek artık tek bir ithal değil, üç ithal ve bir kurulum çağrısıdır. İkincisi bileşim kökünün doğrudan bağ sayısının 1’den 3’e çıkmasıdır; bileşim kökü kaçınılmaz olarak her somut parçayı bilen tek modüldür ve büyümesi bileşen sayısıyla doğru orantılıdır.
Üçüncüsü okunurluk cinsinden değil, izlenmesi gereken çağrı sayısı cinsindendir. Birinci sürümde
ucret işlevinin hangi indirim politikasını kullandığı aynı dosyada, ithal satırında yazılıdır.
İkinci sürümde bu bilgi ücret modülünde yoktur; onu bulmak için bileşim kökü okunmalı ve orada
POLITIKA tablosunun hangi girdisinin seçildiği izlenmelidir. Yani bir sorunun cevabı bir
dosyadan iki dosyaya yayılmıştır.
Bu yüzden bağımlılık enjeksiyonu, bağımlılığın gerçekten değişebilir olduğu yerlerde uygulanır. Bölge tablosu iki sürümde de ücret modülünün içinde bırakılmıştır: bir sabit tablodur, süreç boyunca değişmez ve dışarıdan verilmesi kapanışı düşürmez, yalnız bileşim kökünü büyütür.
Özet
- Bağımlılık enjeksiyonu yaratım sorumluluğunu nesneyi kullanan koddan alır ve bileşim köküne taşır; kalıbın sorusu nesnenin nasıl kurulduğu değil, kimin kurduğudur.
- Ücret modülünün doğrudan bağ sayısı 2’den 0’a, ithal kapanışı boyutu 3’ten 0’a indi; modülün metninde hiçbir somut parçanın adı geçmez.
- Toplam kapanış iki sürümde de 4’tür: kapanış yok olmadı, bileşim köküne taşındı ve kökün doğrudan bağ sayısı 1’den 3’e çıktı.
- İndirim politikası kaldırıldığında birinci sürümde 1 dosya düzenlendi ve o dosya ücret modülüydü; ikinci sürümde 0 dosya düzenlendi.
- Bedel çağrı yerinin uzaması, bileşim kökünün bileşen sayısıyla büyümesi ve bir bağımlılığın hangi somut parça olduğunun ancak bileşim kökü okunarak bulunmasıdır.
Sonraki Adım
Bu konudaki beş düzenek bir nesnenin kim tarafından ve nasıl kurulacağı sorusunu çözdü: tür seçiminin bir yere toplanması, birbirine uyan parçaların birlikte üretilmesi, çok adımlı kurulumun sıraya sokulması, var olan bir nesnenin kopyalanması, tek örneğin nerede tutulacağı ve kurma işinin bileşim köküne taşınması. Hepsinde ortak bir varsayım vardı: kurulan nesne kitaplığın kendi nesnesiydi ve arayüzünü de kitaplık yazıyordu, dolayısıyla parçaların birbirine uyması sağlanabilir bir şeydi. Sonraki konu nesnelerin nasıl birleştirileceğini düzenler ve ilk problem tam bu varsayımın düştüğü yerdir: şeklini kitaplığın belirlemediği bir arayüzle çalışmak. Her dış taşıyıcı sağlayıcısı kendi sözlüğünü, kendi birimini ve hata bildirmenin kendi biçimini getirir; sonraki ders bu uyumsuzluğun tek bir yerde çözülmesini ele alır ve kazancını yine dokunulan dosya sayısıyla ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.