Ders 23 / 30
Kalıp Kötüye Kullanımı
Beş davranışsal kalıbın tek gerçekleştirimle uygulandığı sürümün kalıpsız sürümle aynı ölçülerle karşılaştırılması: dosya sayısı, anlamlı satır, ithal kapanışı, en karmaşık gövde ve çağrı derinliği bedelleri ile sıfır kalan kazançlar; konunun dokuz dersinde ölçülen eşiklerin toplu tablosu.
İçindekiler
Dokuz derste dokuz kalıbın kazancı da bedeli de sayıldı ve ölçümlerin yapıldığı ölçeklerde — strateji dört tarifede, zincir yedi kuralda, arabulucu dört alanda — kazanç her seferinde bedeli aştı. Bu sayılar bir eşiğin varlığını gösteriyor, ama eşiğin altında ne olduğu henüz ölçülmedi.
Bu ders aynı ölçüleri kalıbın aleyhine koşturur. Beş kalıp — strateji, gözlemci, şablon yöntem, durum, sorumluluk zinciri — tek gerçekleştirimle uygulanır ve aynı davranışı üreten kalıpsız sürümle karşılaştırılır. Kalıplı sürümde tek tarife, tek gözlemci, tek değişen adım, iki durum ve tek işleyici var. Beklenen sonuç şudur: bedel kalemleri aynı kalır, kazanç kalemleri sıfırlanır.
Aynı Davranış, İki Sürüm
Ücretlendirme şu adımlardan geçiyor: gönderi onaylanmış olmalı, taban ücret ağırlıktan hesaplanmalı, bölge katsayısı uygulanmalı, hacim indirimi varsa düşülmeli, asgari ücret uygulanmalı ve sonuç günlüğe yazılmalı.
mkdir -p duz kalipli cat > kalipli/durumlar.mjs <<'SON' // kalipli/durumlar.mjs — iki durumlu durum makinesi const durum = (ad, ucretlenebilir) => ({ ad, ucretlenebilir }); export const DURUMLAR = { taslak: durum("taslak", false), onaylandi: durum("onaylandi", true) }; SON cat > kalipli/kurallar.mjs <<'SON' // kalipli/kurallar.mjs — tek isleyicili zincir export const hacim = { ad: "hacim", keser: false, uygunMu: (g) => g.agirlik >= 20, oran: () => 8 }; export const SIRA = [hacim]; SON cat > kalipli/zincir.mjs <<'SON' // kalipli/zincir.mjs — zincir yurutucusu export function indirim(kurallar, gonderi) { let oran = 0; for (const k of kurallar) { if (k.uygunMu(gonderi) === false) continue; oran += k.oran(gonderi); if (k.keser) break; } return oran; } SON cat > kalipli/gozlemci.mjs <<'SON' // kalipli/gozlemci.mjs — tek gozlemcili olay yayimi export const yayinci = () => { const aboneler = []; return { abone: (d) => aboneler.push(d), bildir(olay) { for (const d of aboneler) d(olay); }, }; }; SON
// derinlik.mjs — o anki cagri yiginda kendi dosyalarimiza ait cerceve sayisi export const derinlik = () => new Error().stack.split("\n").filter((s) => s.includes(".mjs:")).length;
// veri.mjs — ucretlendirilecek gonderiler export const GONDERILER = [ { kod: "GN-1", agirlik: 4, bolge: "1", durum: "onaylandi" }, { kod: "GN-2", agirlik: 25, bolge: "2", durum: "onaylandi" }, { kod: "GN-3", agirlik: 9, bolge: "3", durum: "taslak" }, ];
Kalıpsız sürüm tek dosyadır ve altı adımı sırayla yapar.
// duz/ucretle.mjs — ayni davranis tek dosyada, kalipsiz import { derinlik } from "../derinlik.mjs"; const BOLGE = { "1": 100, "2": 130, "3": 175 }; export function ucretlendirici(gunluk) { return { ucretle(gonderi) { if (gonderi.durum !== "onaylandi") return { hata: "onaylanmadi" }; const d = derinlik(); let tutar = Math.round(((2500 + 420 * Math.ceil(gonderi.agirlik)) * BOLGE[gonderi.bolge]) / 100); if (gonderi.agirlik >= 20) tutar -= Math.round((tutar * 8) / 100); tutar = Math.max(tutar, 3900); gunluk.push(`ucret ${gonderi.kod} ${tutar}`); return { tutar, derinlik: d }; }, }; }
Kalıplı sürümde her sorumluluk kendi nesnesindedir. Strateji tek gerçekleştirim taşır ve taban ücreti hesaplarken çağrı derinliğini kaydeder.
// kalipli/tarife.mjs — tek gerceklestirimi olan strateji import { derinlik } from "../derinlik.mjs"; export const standart = { ad: "standart", asgari: 3900, ucretliAgirlik: (g) => Math.ceil(g.agirlik), tabanUcret(a) { this.sonDerinlik = derinlik(); return 2500 + 420 * a; }, };
// kalipli/sablon.mjs — tek adimi degisen sablon yontem const BOLGE = { "1": 100, "2": 130, "3": 175 }; export function hesapla(tarife, gonderi, indirimOrani) { const a = tarife.ucretliAgirlik(gonderi); let tutar = Math.round((tarife.tabanUcret(a) * BOLGE[gonderi.bolge]) / 100); tutar -= Math.round((tutar * indirimOrani) / 100); return Math.max(tutar, tarife.asgari); }
// kalipli/kur.mjs — bilesim koku: bes kalibi birbirine baglar import { standart } from "./tarife.mjs"; import { hesapla } from "./sablon.mjs"; import { DURUMLAR } from "./durumlar.mjs"; import { SIRA } from "./kurallar.mjs"; import { indirim } from "./zincir.mjs"; import { yayinci } from "./gozlemci.mjs"; export function ucretlendirici(gunluk) { const y = yayinci(); y.abone((olay) => gunluk.push(`ucret ${olay.kod} ${olay.tutar}`)); return { ucretle(gonderi) { const d = DURUMLAR[gonderi.durum]; if (d === undefined || d.ucretlenebilir === false) return { hata: "onaylanmadi" }; const tutar = hesapla(standart, gonderi, indirim(SIRA, gonderi)); y.bildir({ kod: gonderi.kod, tutar }); return { tutar, derinlik: standart.sonDerinlik }; }, }; }
Davranış Eşitliği ve Çağrı Derinliği
// kosum.mjs — iki surumu ayni gonderilerle kosar, tutar ve derinligi karsilastirir import { GONDERILER } from "./veri.mjs"; import { ucretlendirici as duzUcret } from "./duz/ucretle.mjs"; import { ucretlendirici as kalipliUcret } from "./kalipli/kur.mjs"; const kosu = (kur) => { const gunluk = []; const u = kur(gunluk); const satir = GONDERILER.map((g) => { const r = u.ucretle(g); return r.hata === undefined ? `${g.kod}=${r.tutar}` : `${g.kod}=${r.hata}`; }); const derinlik = u.ucretle(GONDERILER[0]).derinlik; return { satir, gunluk, derinlik }; }; const a = kosu(duzUcret); const b = kosu(kalipliUcret); console.log(`duz ${a.satir.join(" ")} gunluk=${a.gunluk.length} cagri derinligi=${a.derinlik}`); console.log(`kalipli ${b.satir.join(" ")} gunluk=${b.gunluk.length} cagri derinligi=${b.derinlik}`); console.log(`sapma = ${a.satir.join(" ") === b.satir.join(" ") ? 0 : 1}`);
duz GN-1=4180 GN-2=15548 GN-3=onaylanmadi gunluk=3 cagri derinligi=4 kalipli GN-1=4180 GN-2=15548 GN-3=onaylanmadi gunluk=3 cagri derinligi=6 sapma = 0
Sapma sıfır: iki sürüm aynı üç sonucu, aynı sayıda günlük satırını üretiyor. Beş kalıp davranışa hiçbir şey eklemedi. Çağrı derinliği dörde karşı altı: taban ücretin hesaplandığı satıra ulaşmak için iki çağrı daha geçildi.
Bedelin ve Kazancın Aynı Tabloda Sayılması
// olc.mjs — kalibin bedelini ve tek gerceklestirimdeki kazancini ayni olculerle sayar import { readFileSync, readdirSync } from "node:fs"; import { dirname, join, normalize } from "node:path"; import { SIRA } from "./kalipli/kurallar.mjs"; import { DURUMLAR } from "./kalipli/durumlar.mjs"; const KARAR = /\bif\b|&&|\|\||\?|\bcase\b|\bwhile\b|\bfor\b|\bcontinue\b|\bbreak\b/g; const govde = (yol) => readFileSync(yol, "utf8").replace(/^\/\/.*$/gm, ""); const ithaller = (yol) => [...govde(yol).matchAll(/from "(\.[^"]+)"/g)].map((m) => normalize(join(dirname(yol), m[1]))); function kapanis(kok) { const gorulen = new Set([kok]); const yigit = [kok]; while (yigit.length > 0) { for (const k of ithaller(yigit.pop())) { if (gorulen.has(k) === false) { gorulen.add(k); yigit.push(k); } } } return gorulen.size; } const olc = (dizin, giris) => { const dosyalar = readdirSync(dizin).sort(); let satir = 0; let enKarmasik = 0; for (const d of dosyalar) { const m = govde(`${dizin}/${d}`); satir += m.split("\n").filter((s) => /[A-Za-z]/.test(s)).length; enKarmasik = Math.max(enKarmasik, (m.match(KARAR) ?? []).length + 1); } return { dosya: dosyalar.length, satir, enKarmasik, kapanis: kapanis(giris) }; }; const a = olc("duz", "duz/ucretle.mjs"); const b = olc("kalipli", "kalipli/kur.mjs"); console.log("olcu duz kalipli"); console.log(`dosya sayisi ${String(a.dosya).padEnd(4)} ${b.dosya}`); console.log(`anlamli satir ${String(a.satir).padEnd(4)} ${b.satir}`); console.log(`ithal kapanisi ${String(a.kapanis).padEnd(4)} ${b.kapanis}`); console.log(`en karmasik govde ${String(a.enKarmasik).padEnd(4)} ${b.enKarmasik}`); console.log(`tek kosumda denenen tarife 1 1`); console.log(`kayitli gozlemci sayisi 1 1`); console.log(`zincirdeki isleyici sayisi 1 ${SIRA.length}`); console.log(`durum makinesindeki durum 2 ${Object.keys(DURUMLAR).length}`);
olcu duz kalipli dosya sayisi 1 7 anlamli satir 12 47 ithal kapanisi 2 8 en karmasik govde 3 6 tek kosumda denenen tarife 1 1 kayitli gozlemci sayisi 1 1 zincirdeki isleyici sayisi 1 1 durum makinesindeki durum 2 2
Tablonun üst yarısı bedeldir, alt yarısı kazançtır. Bedel: yedi dosyaya karşı bir, kırk yedi satıra karşı on iki, sekiz dosyalık ithal kapanışına karşı iki, en karmaşık gövdede altıya karşı üç. Son sayı özellikle dikkat çekicidir: kalıplı sürümün en karmaşık gövdesi, kalıpsız sürümün tek gövdesinden daha karmaşıktır. Zincir yürütücüsü tek işleyiciyi gezmek için bir döngü, bir atlama ve bir kesme koşulu taşır; kalıpsız sürümde aynı indirim tek bir koşuldur.
Alt yarı sıfır kazanç gösteriyor. Strateji dersinde ölçülen “tek koşumda denenen tarife sayısı” orada 1’e karşı 3’tü; burada 1’e karşı 1. Gözlemci dersinde giden bağ sayısı 4’ten 0’a inmişti; burada bağ sayısı zaten 1 olduğu için indirilecek bir şey yok, buna karşılık kayıt yapılmadığında sessizce hiçbir şey yapmama yolu açık kalıyor. Durum dersinde temsil uzayı 32’den 6’ya inmişti; iki durumda ikiye karşı iki. Sorumluluk zinciri dersinde en karmaşık gövde yedi kuralda 9’dan 6’ya inmişti; tek kuralda 3’ten 6’ya çıkıyor.
Eşik Tablosu
Dokuz dersin ölçümleri bir arada, her kalıbın karşılığını vermeye başladığı ölçekle birlikte:
| Kalıp | Ölçülen kazanç | Karşılığını verdiği ölçek |
|---|---|---|
| Strateji | tek koşumda denenen algoritma 1 → 3; yeni tarifede düzenlenen dosya 2 → 0 | ikinci algoritma |
| Gözlemci | giden bağ 4 → 0, ithal kapanışı 5 → 1 | ikinci dinleyici |
| Komut | geri alınan işlem 0 → 3, denetim izi kaydı 0 → 8 | geri alma ya da kayıt isteği |
| Şablon yöntem | yinelenen satır 6 → 0, yeni adımda dosya 3 → 1 | ikinci gerçekleştirim |
| Durum | geçersiz geçiş 3 → 0, karmaşıklık 22 → 2, temsil uzayı 32 → 6 | üçüncü durum |
| Sorumluluk zinciri | en karmaşık gövde 9 → 6 (yedi kuralda), 6 → 6 (dört kuralda) | beşinci kural |
| Yineleyici | gezinme kodu taşıyan yer 5 → 1; sıra değişiminde dosya 5 → 1 | ikinci gezinme işlemi |
| Ziyaretçi | sessizce yanlış sonuç veren işlem 3 → 0 | ikinci düğüm türü |
| Arabulucu | alanlar arası bağ 6 → 3, güncelleme çağrısı 8 → 4 | üçüncü alan |
| Anımsatıcı | dışarıdan erişilen iç alan 8 → 0, fiş alan sayısı 4 → 1 | durum saklama isteği |
| Yorumlayıcı | düzenlenen kaynak dosyası 1 → 0 | kuralı geliştirici dışında biri yazacaksa |
| Boş nesne | boş değer denetimi 2 → 0, hata fırlatan çağrı yeri 1 → 0 | ikinci çağrı yeri |
Sağ sütun tek bir cümlenin farklı biçimleridir: kalıp, ayırdığı eksende ikinci bir örnek gerçekten varsa karşılığını verir. İkinci algoritma yoksa strateji bir dosya ve bir dolaylılık basamağıdır; ikinci dinleyici yoksa gözlemci bir bileşim kökü ve sessiz bir hata yoludur; ikinci gerçekleştirim yoksa şablon yöntem bir boş kancadır. Kalıbı gerekçelendiren şey kalıbın adı ya da bilinirliği değil, sol sütundaki sayının sıfırdan farklı olmasıdır.
Bu ölçüt kalıbı sonradan uygulamayı da mümkün kılar. Kalıpsız sürüm, ikinci tarife geldiği gün strateji sürümüne çevrilebilir; birinci dersteki ölçüm bu çevirinin iki dosya ve on üç satır tuttuğunu göstermişti. Erken uygulanmış kalıbı geri almak ise yedi dosyayı bire indirmek demektir ve bu iş, çevirinin tersinden pahalıdır. Kalıbın bedeli peşin, kazancı vadelidir; ölçü tutulmadığında ödenen bedel geri alınmaz.
Özet
- Beş davranışsal kalıp tek gerçekleştirimle uygulandığında kalıpsız sürümle birebir aynı sonucu üretti; sapma 0 çıktı, yani kalıplar davranışa hiçbir şey eklemedi.
- Bedel: dosya sayısı 1’den 7’ye, anlamlı satır 12’den 47’ye, ithal kapanışı 2’den 8’e, çağrı derinliği 4’ten 6’ya çıktı.
- Kalıplı sürümün en karmaşık gövdesi 6, kalıpsız sürümün tek gövdesi 3; tek işleyicili zincir karmaşıklığı düşürmek yerine yükseltti.
- Kazanç kalemleri sıfırlandı: tek koşumda denenen tarife 1’e karşı 1, kayıtlı gözlemci 1’e karşı 1, durum sayısı 2’ye karşı 2.
- Her kalıp, ayırdığı eksende ikinci bir örnek gerçekten varsa karşılığını verir; bedel peşin, kazanç vadelidir ve erken uygulanmış kalıbı geri almak uygulamaktan pahalıdır.
Sonraki Adım
Bu konuya kadarki kalıpların hepsi nesne düzeyinde çalıştı: bir sorumluluk alındı, ayrı bir nesneye verildi ve bedeli dolaylılık basamağıyla ödendi. Eşik tablosunun tamamı bu ölçekte kuruldu — bir algoritma, bir dinleyici, bir düğüm türü. Sıradaki konu ölçeği bir basamak yukarı çıkarır. Soru artık bir sorumluluğun hangi nesneye verileceği değil, bir uygulamanın katman düzeninde iş mantığının nereye yerleştiği ve kalıcılıkla arasındaki sınırın hangi kalıplarla kurulduğudur. İlk soru en temel olanıdır: aynı iş kuralı bir senaryo yordamının içinde mi yaşamalı, yoksa veriyi taşıyan nesnenin üzerinde mi. Sonraki ders bu iki düzenlemeyi kuralın kaç ayrı yerde uygulandığını sayarak karşılaştırır ve ikinci düzenlemenin dosya, ad ve dolaylılık bedelini aynı biçimde ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.