İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat