Ders 12 / 19
Soyutlamalara Karşı Programlama
Somut tipe bağlanmanın maliyetinin sayılması: ikinci bir fiyat listesi eklendiğinde düzenlenen dosya ve dokunulan satır sayısının iki yerleşimde karşılaştırılması, seçim yapan yer sayısının bulunması ve sahte bir fiyat listesinin yalnız soyutlamaya bağlı istemcide etkili olduğunun gösterilmesi.
İçindekiler
Bir önceki dersin yalıtılmış yerleşiminde üç istemci de fiyat modülünü adıyla ithal ediyor ve işlevini doğrudan çağırıyordu. Fiyat listesi tek olduğu sürece bu görünmez. Sözleşmeli müşteriler için ikinci bir liste geldiğinde soru keskinleşir: istemci hangi listenin kullanılacağını nereden bilecek?
Soyutlamalara karşı programlama, istemcinin somut bir modüle değil, birden çok gerçekleştirimin karşılayabileceği bir sözleşmeye bağlanmasıdır. Bağımlılığın ters çevrilmesi dersi sözleşmenin hangi pakette tanımlandığını ölçmüştü; buradaki ölçü istemci tarafında: gerçekleştirim değiştiğinde ya da ikincisi eklendiğinde kaç satıra dokunuluyor?
Somut Modüle Bağlanan Yerleşim
mkdir -p somut soyut
// kayit.mjs — iki yerlesimin de kullandigi ornek gonderiler export const KAYITLAR = [ { kod: "GN-1", agirlik: 0.8, adres: "34100", sozlesmeli: false }, { kod: "GN-2", agirlik: 3.0, adres: "06500", sozlesmeli: true }, { kod: "GN-3", agirlik: 12.0, adres: "65200", sozlesmeli: true }, ];
// somut/fiyat.mjs — standart fiyat listesi const KADEME = [[1, 3900], [5, 6400], [20, 11800]]; const BOLGE = { "34": 100, "06": 115, "65": 140 }; export function kurusHesabi(gonderi) { const taban = KADEME.find(([ust]) => gonderi.agirlik <= ust)?.[1] ?? 15000; return Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 160)) / 100); }
// somut/web.mjs — somut fiyat modulunu adiyla ithal eder import { kurusHesabi } from "./fiyat.mjs"; export const satir = (gonderi) => `${gonderi.kod} ${kurusHesabi(gonderi)} kurus`;
// somut/toplu.mjs — somut fiyat modulunu adiyla ithal eder import { kurusHesabi } from "./fiyat.mjs"; export const satirlar = (gonderiler) => gonderiler.map((g) => `${g.kod} ${kurusHesabi(g)} kurus`);
// somut/rapor.mjs — somut fiyat modulunu adiyla ithal eder import { kurusHesabi } from "./fiyat.mjs"; export const toplamSatiri = (gonderiler) => `TOPLAM ${gonderiler.reduce((t, g) => t + kurusHesabi(g), 0)} kurus`;
// somut/kur.mjs — bilesim koku import { KAYITLAR } from "../kayit.mjs"; import { satir } from "./web.mjs"; import { satirlar } from "./toplu.mjs"; import { toplamSatiri } from "./rapor.mjs"; console.log(satir(KAYITLAR[1])); console.log(satirlar(KAYITLAR).join(" | ")); console.log(toplamSatiri(KAYITLAR));
Soyutlamaya Bağlanan Yerleşim
İkinci yerleşimde fiyat listesi bir nesnedir ve istemcilere dışarıdan verilir. İstemciler
kurusHesabi adında bir yöntemin var olduğunu bilir; hangi listenin geldiğini bilmez.
// soyut/fiyat-standart.mjs — sozlesmeyi karsilayan standart liste const KADEME = [[1, 3900], [5, 6400], [20, 11800]]; const BOLGE = { "34": 100, "06": 115, "65": 140 }; export const standart = { ad: "standart", kurusHesabi(gonderi) { const taban = KADEME.find(([ust]) => gonderi.agirlik <= ust)?.[1] ?? 15000; return Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 160)) / 100); }, };
// soyut/web.mjs — fiyati disaridan alir, gerceklestirimin adini bilmez export const satir = (fiyat, gonderi) => `${gonderi.kod} ${fiyat.kurusHesabi(gonderi)} kurus`;
// soyut/toplu.mjs — fiyati disaridan alir export const satirlar = (fiyatSec, gonderiler) => gonderiler.map((g) => `${g.kod} ${fiyatSec(g).kurusHesabi(g)} kurus`);
// soyut/rapor.mjs — fiyati disaridan alir export const toplamSatiri = (fiyatSec, gonderiler) => `TOPLAM ${gonderiler.reduce((t, g) => t + fiyatSec(g).kurusHesabi(g), 0)} kurus`;
// soyut/kur.mjs — bilesim koku: hangi fiyat listesinin kullanilacagini yalniz burasi bilir import { KAYITLAR } from "../kayit.mjs"; import { standart } from "./fiyat-standart.mjs"; import { satir } from "./web.mjs"; import { satirlar } from "./toplu.mjs"; import { toplamSatiri } from "./rapor.mjs"; const fiyatSec = () => standart; console.log(satir(fiyatSec(KAYITLAR[1]), KAYITLAR[1])); console.log(satirlar(fiyatSec, KAYITLAR).join(" | ")); console.log(toplamSatiri(fiyatSec, KAYITLAR));
node somut/kur.mjs node soyut/kur.mjs
GN-2 7360 kurus GN-1 3900 kurus | GN-2 7360 kurus | GN-3 16520 kurus TOPLAM 27780 kurus GN-2 7360 kurus GN-1 3900 kurus | GN-2 7360 kurus | GN-3 16520 kurus TOPLAM 27780 kurus
İkinci Gerçekleştirim
Sözleşmeli müşteri listesi geliyor: kademe ücretleri ve bölge katsayıları farklı. İki ağaç kopyalanır ve gereksinim ikisine de uygulanır.
cp -r somut somut-yeni cp -r soyut soyut-yeni
// somut-yeni/fiyat-sozlesmeli.mjs — sozlesmeli musteri listesi const KADEME = [[1, 3200], [5, 5300], [20, 9700]]; const BOLGE = { "34": 100, "06": 110, "65": 130 }; export function kurusHesabi(gonderi) { const taban = KADEME.find(([ust]) => gonderi.agirlik <= ust)?.[1] ?? 13000; return Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 150)) / 100); }
// somut-yeni/web.mjs — iki somut modulu de ithal eder ve arasinda secim yapar import { kurusHesabi } from "./fiyat.mjs"; import { kurusHesabi as sozlesmeliHesabi } from "./fiyat-sozlesmeli.mjs"; export const satir = (gonderi) => `${gonderi.kod} ${gonderi.sozlesmeli ? sozlesmeliHesabi(gonderi) : kurusHesabi(gonderi)} kurus`;
// somut-yeni/toplu.mjs — ayni secim ikinci kez import { kurusHesabi } from "./fiyat.mjs"; import { kurusHesabi as sozlesmeliHesabi } from "./fiyat-sozlesmeli.mjs"; export const satirlar = (gonderiler) => gonderiler.map((g) => `${g.kod} ${g.sozlesmeli ? sozlesmeliHesabi(g) : kurusHesabi(g)} kurus`);
// somut-yeni/rapor.mjs — ayni secim ucuncu kez import { kurusHesabi } from "./fiyat.mjs"; import { kurusHesabi as sozlesmeliHesabi } from "./fiyat-sozlesmeli.mjs"; export const toplamSatiri = (gonderiler) => `TOPLAM ${gonderiler.reduce((t, g) => t + (g.sozlesmeli ? sozlesmeliHesabi(g) : kurusHesabi(g)), 0)} kurus`;
Soyut yerleşimde istemciler açılmaz. Yeni liste aynı sözleşmeyi karşılayan ikinci bir nesnedir ve seçim bileşim kökünde yapılır.
// soyut-yeni/fiyat-sozlesmeli.mjs — ayni sozlesmeyi karsilayan ikinci liste const KADEME = [[1, 3200], [5, 5300], [20, 9700]]; const BOLGE = { "34": 100, "06": 110, "65": 130 }; export const sozlesmeli = { ad: "sozlesmeli", kurusHesabi(gonderi) { const taban = KADEME.find(([ust]) => gonderi.agirlik <= ust)?.[1] ?? 13000; return Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 150)) / 100); }, };
// soyut-yeni/kur.mjs — bilesim koku: hangi fiyat listesinin kullanilacagini yalniz burasi bilir import { KAYITLAR } from "../kayit.mjs"; import { standart } from "./fiyat-standart.mjs"; import { sozlesmeli } from "./fiyat-sozlesmeli.mjs"; import { satir } from "./web.mjs"; import { satirlar } from "./toplu.mjs"; import { toplamSatiri } from "./rapor.mjs"; const fiyatSec = (g) => (g.sozlesmeli ? sozlesmeli : standart); console.log(satir(fiyatSec(KAYITLAR[1]), KAYITLAR[1])); console.log(satirlar(fiyatSec, KAYITLAR).join(" | ")); console.log(toplamSatiri(fiyatSec, KAYITLAR));
Bedelin Sayılması
node somut-yeni/kur.mjs node soyut-yeni/kur.mjs for k in somut soyut; do echo "$k: duzenlenen dosya = $(diff -rq $k $k-yeni | grep -c '^Files')" \ " yeni dosya = $(diff -rq $k $k-yeni | grep -c '^Only in')" \ " dokunulan satir = $(diff -rU0 $k $k-yeni | grep -cE '^[+-][^+-]')" \ " secim yapan dosya = $(grep -l 'sozlesmeli ?' $k-yeni/*.mjs | wc -l | tr -d ' ')" done
GN-2 5830 kurus GN-1 3900 kurus | GN-2 5830 kurus | GN-3 12610 kurus TOPLAM 22340 kurus GN-2 5830 kurus GN-1 3900 kurus | GN-2 5830 kurus | GN-3 12610 kurus TOPLAM 22340 kurus somut: duzenlenen dosya = 3 yeni dosya = 1 dokunulan satir = 18 secim yapan dosya = 3 soyut: duzenlenen dosya = 1 yeni dosya = 1 dokunulan satir = 5 secim yapan dosya = 1
Aynı gereksinim, aynı sonuç, farklı maliyet: on sekiz satıra karşı beş satır, üç düzenlenen dosyaya karşı bir. Son sütun farkın nedenini veriyor — somut yerleşimde seçim üç ayrı yerde yapılıyor. Üçüncü bir liste eklendiğinde bu üç yerin üçü de yeniden düzenlenecek; soyut yerleşimde düzenlenecek yer bileşim kökü olarak kalacak.
Yerine Koyabilme
İkinci ölçü, gerçekleştirimin sınama sırasında değiştirilebilmesidir. Aynı istemciye sabit değer üreten sahte bir fiyat listesi verilir.
// sahte-dene.mjs — istemciler sahte bir fiyat listesiyle calisabiliyor mu const SAHTE = { ad: "sahte", kurusHesabi: () => 1000 }; const G = { kod: "GN-9", agirlik: 3.0, adres: "06500", sozlesmeli: false }; const somutWeb = await import("./somut/web.mjs"); const soyutWeb = await import("./soyut/web.mjs"); console.log("somut ->", somutWeb.satir(G)); console.log("soyut ->", soyutWeb.satir(SAHTE, G));
node sahte-dene.mjs
somut -> GN-9 7360 kurus soyut -> GN-9 1000 kurus
Somut yerleşimde sahte listenin verilecek bir yeri yok; istemci onu görmezden geldi ve gerçek fiyatı hesapladı. Soyut yerleşimde aynı istemci sahteyi kullandı. Fark bir kolaylık değil, yapısal: somut ithal bir seçim noktası bırakmaz.
İlkenin Bedeli
Soyutlama karşılıksız değildir. Soyut yerleşimde üç istemcinin imzası bir parametre uzadı ve
fiyatSec işlevi bileşim kökünde ek bir dolaylılık kurdu. Gerçekleştirim tek kalacaksa bu
fazlalık geri kazanılmaz: ölçüm, ikinci bir gerçekleştirim geldiğinde kazanç verdi, hiç
gelmeseydi yalnız parametre sayısını artırmış olurdu.
Ölçüt bu yüzden gerçekleştirim sayısının geçmişidir. Bir modülün ikinci bir gerçekleştirimi hiç istenmediyse ve sınamada da değiştirilmesi gerekmiyorsa somut ithal doğru seçimdir. Sınamada değiştirilmesi gerekiyorsa gerçekleştirim sayısı zaten ikidir; sahte de bir gerçekleştirimdir.
Özet
- Soyutlamalara karşı programlama, istemcinin somut bir modüle değil, birden çok gerçekleştirimin karşılayabileceği bir sözleşmeye bağlanmasıdır.
- İkinci fiyat listesi eklendiğinde somut yerleşimde 3 dosya ve 18 satır, soyut yerleşimde 1 dosya ve 5 satır dokunuldu.
- Farkın kaynağı seçim noktasıdır: somut yerleşimde seçim 3 dosyada, soyut yerleşimde 1 dosyada yapılıyor.
- Sahte bir fiyat listesi yalnız soyut yerleşimde etkili oldu; somut ithal, gerçekleştirimin değiştirileceği bir nokta bırakmıyor.
- Soyutlamanın bedeli parametre sayısı ve bir dolaylılık katmanıdır; ikinci gerçekleştirim hiç gelmeyecekse bu bedel geri kazanılmaz.
Sonraki Adım
Soyut yerleşimde bileşim kökü hangi fiyat listesinin kullanılacağına karar veriyor, ama
istemcileri hâlâ kendisi çağırıyor: önce satir, sonra satirlar, sonra toplamSatiri.
Adımların sırası ve sayısı çağıranın gövdesinde duruyor. Bu akışa yeni bir adım eklemek —
sözleşmeli müşteri indirimi, sigorta primi, bir denetim satırı — çağıranı her seferinde
düzenlemeyi gerektirir. Sonraki ders çağıran ile çağrılan arasındaki yönü tersine çevirir:
adımların sırasını bir iskelete, adımların kendisini iskelete verilen parçalara taşır ve yeni
bir adım eklemenin maliyetini iki düzende karşılaştırır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.