İçeriğe geç
academia.sh

Ders 30 / 30

Tip Nesnesi ve Nitelik Varyantları

Türün çalışma zamanında modellenmesi: her hizmet türü için sınıf yazan düzen ile türü veri olarak taşıyan tip nesnesinin karşılaştırılması, tür adı taşıyan kod dosyası sayısının ve yeni tür eklemenin maliyetinin sayılması, nitelik torbasının çalışma zamanında doğrulanması ve kaybedilen yazım zamanı denetiminin bedeli.

İçindekiler

Buraya kadarki altı kalıp gönderi türünün tek olduğunu varsaydı. Kitaplığa ikinci bir hizmet türü girdiğinde — soğuk zincir gönderisi, değerli eşya gönderisi, belge gönderisi — her türün kendi nitelikleri ve kendi ücret çarpanı olur. Türü koda yazmak, tür sayısı arttıkça düzenlenen dosya sayısını artıran bir karardır; türler bir veri kaynağından geliyorsa kod yazmak zaten olanaksızdır.

Tip nesnesi (type object) türü bir sınıf olarak değil, bir nesne olarak modeller: türün adı, gerektirdiği nitelikler ve ücret davranışı veri hâline gelir. Nitelik varyantları ise bunun ikinci yarısıdır: her türün farklı bir nitelik kümesi taşıması, gönderi kaydının sabit alanlar yerine bir nitelik torbası (property bag) tutmasıyla çözülür ve torbanın geçerliliği tip nesnesine sorulur.

Sınıf Başına Tür

İlk düzende her hizmet türü bir sınıftır; nitelik listesi ve çarpan sınıfın içinde durur.

// siniflar/turler.mjs — her hizmet turu bir sinif; nitelik listesi ve carpan kodda
export class Standart {
  static nitelikler = [];
  ucret(g) { return Math.round(g.taban); }
}

export class SogukZincir {
  static nitelikler = ["sicaklik"];
  ucret(g) { return Math.round(g.taban * 1.6); }
}

export class Degerli {
  static nitelikler = ["sigortaDegeri"];
  ucret(g) { return Math.round(g.taban * 1.25 + g.nitelikler.sigortaDegeri * 0.01); }
}
// siniflar/kayit.mjs — tur anahtarini sinifa baglayan kayit
import { Standart, SogukZincir, Degerli } from "./turler.mjs";

const KAYIT = { "standart": Standart, "soguk-zincir": SogukZincir, "degerli": Degerli };

export function ucret(g) {
  const Tur = KAYIT[g.tur];
  if (Tur === undefined) throw new RangeError(`bilinmeyen hizmet turu: ${g.tur}`);
  return new Tur().ucret(g);
}

Düzenin kazancı her türün davranışını kendi metninde tutmasıdır. Kaybı, tür kümesinin kodun bir parçası olmasıdır: yeni tür iki dosyayı da düzenlemek anlamına gelir.

Tip Nesnesi

İkinci düzende tür bir nesnedir. Kod hiçbir tür adını bilmez; yalnız bir türün nasıl bir şey olduğunu bilir.

// tip/tip-nesnesi.mjs — tur bir nesne olarak modellenir; kodda hicbir tur adi gecmez
export class TipNesnesi {
  constructor({ ad, nitelikler, carpan, ek }) {
    Object.assign(this, { ad, nitelikler, carpan, ek: ek ?? null });
  }
  dogrula(g) {
    const torba = g.nitelikler ?? {};
    return [
      ...this.nitelikler.filter((n) => n in torba === false).map((n) => `eksik nitelik: ${n}`),
      ...Object.keys(torba).filter((n) => this.nitelikler.includes(n) === false).map((n) => `taninmayan nitelik: ${n}`),
    ];
  }
  ucret(g) {
    const ek = this.ek === null ? 0 : (g.nitelikler?.[this.ek.alan] ?? 0) * this.ek.oran;
    return Math.round(g.taban * this.carpan + ek);
  }
}

export const tipKatalogu = (json) => new Map(JSON.parse(json).map((t) => [t.ad, new TipNesnesi(t)]));

Türlerin kendisi veridir. Aşağıdaki dosya bir kod dosyası değil, veri taşıyan bir dosyadır; aynı metin bir yapılandırma kaydından ya da bir tablodan da gelebilirdi.

// tip/tur-verisi.mjs — hizmet turleri veri olarak: her satir bir tur
export const TUR_JSON = `[
  { "ad": "standart", "nitelikler": [], "carpan": 1 },
  { "ad": "soguk-zincir", "nitelikler": ["sicaklik"], "carpan": 1.6 },
  { "ad": "degerli", "nitelikler": ["sigortaDegeri"], "carpan": 1.25, "ek": { "alan": "sigortaDegeri", "oran": 0.01 } }
]`;

ek alanı nitelik varyantlarının ücrete karışma biçimidir: değerli eşyada sigorta değerinin yüzde biri ücrete eklenir, öteki türlerde böyle bir kural yoktur. Sınıf tabanlı düzende bu kural bir yöntem gövdesiydi; burada iki alanlı bir kayıt.

Ölçüm

Üç şey sayılır: tür adının kaç kod dosyasında durduğu, yeni bir türün eklenmesinin neye mal olduğu ve geçersiz niteliklerin kaçının yakalandığı.

// tur-say.mjs — tur adinin kac kod dosyasinda durdugu ve yeni turun eklenme maliyeti
import { readFileSync } from "node:fs";
import { ucret as sinifUcreti } from "./siniflar/kayit.mjs";
import { tipKatalogu } from "./tip/tip-nesnesi.mjs";
import { TUR_JSON } from "./tip/tur-verisi.mjs";

const TUR_ADI = /Standart|SogukZincir|Degerli|"(standart|soguk-zincir|degerli)"/;
const KOD = { "sinif tabanli": ["siniflar/turler.mjs", "siniflar/kayit.mjs"], "tip nesnesi ": ["tip/tip-nesnesi.mjs"] };

for (const [ad, dosyalar] of Object.entries(KOD)) {
  const metinler = dosyalar.map((d) => readFileSync(d, "utf8"));
  const dokunan = metinler.filter((m) => TUR_ADI.test(m)).length;
  const liste = metinler.reduce((t, m) => t + [...m.matchAll(/static nitelikler|"nitelikler"/g)].length, 0);
  console.log(`${ad}: tur adi tasiyan kod dosyasi = ${dokunan} / ${dosyalar.length}, kodda nitelik listesi = ${liste}`);
}
console.log(`tur verisi  : ${JSON.parse(TUR_JSON).length} tur, tip/tur-verisi.mjs veri dosyasidir`);

const BELGE = `{ "ad": "belge", "nitelikler": ["sayfa"], "carpan": 0.7 }`;
const katalog = tipKatalogu(`[${TUR_JSON.slice(1, -1).trim()},\n  ${BELGE}\n]`);
const g = { tur: "belge", taban: 8640, nitelikler: { sayfa: 4 } };

let sinifSonuc;
try { sinifSonuc = String(sinifUcreti(g)); } catch (e) { sinifSonuc = `hata: ${e.message}`; }
console.log(`\nyeni tur "belge" eklendi (1 veri satiri, 0 kod satiri)`);
console.log(`  sinif tabanli -> ${sinifSonuc}`);
console.log(`  tip nesnesi   -> ${katalog.get("belge").ucret(g)}, katalogdaki tur sayisi = ${katalog.size}`);

const HATALILAR = [
  { tur: "soguk-zincir", taban: 8640, nitelikler: {} },
  { tur: "degerli", taban: 8640, nitelikler: { sigortaDegeri: 50000, sicaklik: -18 } },
  { tur: "standart", taban: 8640, nitelikler: { sayfa: 4 } },
];
let yakalanan = 0, sinifYakalanan = 0;
for (const h of HATALILAR) {
  const bulgular = katalog.get(h.tur).dogrula(h);
  if (bulgular.length > 0) yakalanan += 1;
  let sinif;
  try { sinif = String(sinifUcreti(h)); } catch (e) { sinif = `hata: ${e.name}`; sinifYakalanan += 1; }
  console.log(`  ${h.tur.padEnd(13)} tip nesnesi: ${bulgular.join("; ") || "bulgu yok"}  | sinif tabanli: ${sinif}`);
}
console.log(`dogrulama: tip nesnesi ${yakalanan}/${HATALILAR.length}, sinif tabanli ${sinifYakalanan}/${HATALILAR.length}`);
node tur-say.mjs
sinif tabanli: tur adi tasiyan kod dosyasi = 2 / 2, kodda nitelik listesi = 3
tip nesnesi : tur adi tasiyan kod dosyasi = 0 / 1, kodda nitelik listesi = 0
tur verisi  : 3 tur, tip/tur-verisi.mjs veri dosyasidir

yeni tur "belge" eklendi (1 veri satiri, 0 kod satiri)
  sinif tabanli -> hata: bilinmeyen hizmet turu: belge
  tip nesnesi   -> 6048, katalogdaki tur sayisi = 4
  soguk-zincir  tip nesnesi: eksik nitelik: sicaklik  | sinif tabanli: 13824
  degerli       tip nesnesi: taninmayan nitelik: sicaklik  | sinif tabanli: 11300
  standart      tip nesnesi: taninmayan nitelik: sayfa  | sinif tabanli: 8640
dogrulama: tip nesnesi 3/3, sinif tabanli 0/3

İlk iki satır kazancı veriyor: sınıf tabanlı düzende iki kod dosyasının ikisi de tür adı taşıyor ve nitelik listesi kodda üç yerde duruyor. Tip nesnesi düzeninde tür adı taşıyan kod dosyası 0, kodda nitelik listesi 0; üçünün üçü de veriye geçmiş durumda.

Dördüncü tür bunun sonucudur. belge türü koda hiç girmeden eklendi: 1 veri satırı, 0 kod satırı. Sınıf tabanlı düzen aynı gönderiyi ücretlendiremedi ve bilinmeyen tür hatası verdi. Katalog dört türe çıktı ve yeni türün ücreti hesaplandı.

Son üç satır bedeli gösteriyor. Sınıf tabanlı düzen üç hatalı gönderinin üçünü de sessizce ücretlendirdi: eksik nitelikli soğuk zincir gönderisi 13824, tanınmayan nitelik taşıyan iki gönderi de bir tutar üretti. Tip nesnesi üçünü de yakaladı, ama bunu çalışma zamanında ve kendi yazdığı doğrulamayla yaptı.

Bedel ve Ne Zaman Uygulanmaz

Tip nesnesinin ödediği şey yazım zamanında var olan denetimdir. Sınıf tabanlı düzende bir tür için yanlış bir nitelik adı yazıldığında hata sınıfın metninde görünür; tip nesnesi düzeninde nitelik adları dizgeye dönüşür ve hiçbir okuma sırasında denetlenmez. Bu yüzden kalıp bir doğrulama yükümlülüğü getirir: yukarıdaki ölçümde üç bulgu doğrulama işlevi yazıldığı için yakalandı, kalıbın kendisi yakalamadı.

İkinci bedel çağrı yerinin belirsizleşmesidir. Sınıf tabanlı düzende bir türün ücret hesabını okumak için o türün sınıfına gidilir; tip nesnesi düzeninde tek bir ucret gövdesi bütün türleri hesaplar ve türe özgü davranış ek gibi alanlarla ifade edilir. Türe özgü davranış karmaşıklaştıkça bu alanların sayısı artar ve tip nesnesi kendi içinde küçük bir yorumlayıcıya dönüşmeye başlar.

Buradan uygulanma sınırı çıkar. Tür sayısı sabit ve azsa, her türün davranışı gerçekten farklı bir algoritmaysa, kalıp karşılıksızdır: nitelik listesi zaten 3 yerde durur ve yeni tür gelmediği için düzenlenen dosya sayısı 0 kalır. Bu durumda türü çok biçimlilikle ifade etmek hem yazım zamanı denetimini korur hem davranışı kendi dosyasında tutar.

Ters koşullar kalıbı gerektirir: tür kümesi çalışma zamanında geliyorsa, kullanıcı yeni tür tanımlıyorsa, ya da türler yalnız veriyle ayrışıp davranışları ortak bir kuralın parametreleriyse. Ölçüt tek cümledir: türler arasındaki fark veriyle ifade edilebiliyorsa tip nesnesi, kodla ifade edilmek zorundaysa çok biçimlilik.

Özet

  • Tip nesnesi türü bir sınıf olarak değil bir nesne olarak modeller; nitelik varyantları türe göre değişen alanları bir nitelik torbasında toplar ve geçerliliğini tip nesnesine sorar.
  • Sınıf tabanlı düzende iki kod dosyasının ikisi de tür adı taşıyordu ve nitelik listesi kodda 3 yerde duruyordu; tip nesnesi düzeninde ikisi de 0’a indi.
  • Dördüncü tür 1 veri satırı ve 0 kod satırıyla eklendi ve ücreti hesaplandı; sınıf tabanlı düzen aynı gönderi için bilinmeyen tür hatası verdi.
  • Bedel yazım zamanı denetiminin kaybıdır: üç hatalı gönderiyi sınıf tabanlı düzen sessizce ücretlendirdi, tip nesnesi üçünü de yakaladı ama bunu elle yazılmış bir doğrulamayla yaptı.
  • Ölçüt türler arasındaki farkın nerede ifade edilebildiğidir: veriyle ifade edilebiliyorsa tip nesnesi, kodla ifade edilmek zorundaysa çok biçimlilik.

Kurs Kapanışı

Kurs dört problem sınıfını dolaştı. Yaratımsal kalıplar nesnenin nasıl ve nerede yaratıldığını sordu: Kalıp Kavramı dersi kalıbın anlatısını problem, çözüm ve sonuçlar üçlüsü olarak kurdu ve ilk ölçümü verdi — tür adının geçtiği yer sayısı 6’dan 2’ye inerken dosya sayısı 3’ten 5’e, ithal kenarı sayısı 2’den 7’ye çıktı. Yapısal kalıplar nesnelerin nasıl birleştirildiğini ve uyumsuz arayüzlerin nasıl köprülendiğini sordu; Bağdaştırıcı dersi kazancı dış sözlüğe dokunan dosya oranıyla, bedeli 3 dosya, 4 satır ve bir dolaylılık basamağıyla saydı. Davranışsal kalıplar sorumluluğun nesneler arasında nasıl dağıtıldığını sordu; Strateji dersinde dördüncü tarife gömülü sürümde 2 dosya ve 13 satır düzenlemeye mal olurken strateji sürümünde 0 dosya düzenlemesiyle eklendi, karşılığında izlenmesi gereken gövde sayısı 1’den 3’e çıktı.

Kurumsal uygulama kalıpları ölçeği bir uygulamanın katman düzenine çıkardı. Aynı ölçüm biçimi orada da yürüdü: kural uygulama noktası 3’ten 1’e, alan modülündeki kalıcılık izi dört, iki ve beş adımdan 0’a, uygulama katmanındaki tekrarlanan senaryo adımı 7’den 0’a, iç model değiştiğinde bozulan istemci sayısı 3’ten 0’a, tür adı taşıyan kod dosyası 2’den 0’a indi. Her derste bedel de aynı cümleyle sayıldı: eklenen dosya, eklenen ad, dolaylılık basamağı, izlenmesi gereken çağrı.

Kursun başında kurulan iddia budur: kalıp, ölçüyü iyileştirdiği ölçüde kalıptır. Otuz dersin hiçbirinde kalıp kendiliğinden iyi sayılmadı; her biri bir sayıyı düşürdüğü için seçildi ve her birinin “ne zaman uygulanmaz” bölümü aynı sayının nasıl sıfırlandığını gösterdi. Kazanç ile bedelin oranı bağlama bağlıdır ve bağlam ölçülebilir: kaç senaryo aynı kurala dokunuyor, kaç istemci aynı sınırdan geçiyor, kaç tür bekleniyor. Bu sorular yanıtlanmadan bir kalıp seçilemez.

Kalıplar bir soruyu açıkta bıraktı. Hepsi çözümün biçimi üzerineydi: kural nereye yazılır, nesne nasıl yaratılır, sınır nereye çizilir. Hiçbiri kuralın ne olduğunu sormadı. Gönderi, tarife, bölge, rota ve indirim sözcüklerinin ne anlama geldiği, hangilerinin bir arada değiştiği, aynı sözcüğün iki takımda iki farklı şeyi adlandırıp adlandırmadığı bu kursun dışında kaldı. Sonraki kurs Alan Odaklı Tasarım tam bu soruyu alır: alan dilinin ve sınırlı bağlamların birer modelleme aracı hâline gelmesi, yani bir kod tabanının yapısının alanın kendi ayrımlarından türetilmesi.

İ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