İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat