Ders 01 / 30
Kalıp Kavramı
Bir kalıbın problem, çözüm ve sonuçlar üçlüsü olarak tanımlanması: gönderi kitaplığında iki ayrı dersten tanıdık olan kayıt çözümünün taşıyıcı seçimine uygulanması, aynı çıktıyı veren iki sürümün dosya sayısı, ithal kenarı sayısı, tür adının geçtiği yer sayısı ve en uzun ithal yolu ile ölçülmesi, kazancın ve bedelin ayrı ayrı sayılması.
İçindekiler
Tasarım İlkeleri kursu bir ölçüt kümesiyle kapandı: sınır dar olsun, bağ kararlı yöne aksın, birlikte değişenler bir arada dursun. Bu cümlelerin hiçbiri bir çözüm vermez. Bir modülün açık–kapalı ilkesini karşılamadığı ölçülebilir, ama ilke o modülün yerine ne yazılacağını söylemez. Kararı veren şey, aynı problemin daha önce çözülmüş olmasıdır.
Aynı problem farklı kod tabanlarında defalarca aynı biçimde çözülmüşse, o çözümün bir adı, bilinen bir uygulanış biçimi ve bilinen sonuçları vardır. Kalıp (pattern) budur: bir kod parçası değil, tekrar eden bir problemin adlandırılmış çözümü ve o çözümün getirdiği sonuçların listesi. Bu ders üçlünün parçalarını tanımlar, gönderi ücretlendirme kitaplığında daha önce iki kez bulunmuş bir çözümü taşıyıcı seçimine uygular ve sonuçlarını sayar.
Aynı Çözümün İkinci Kez Bulunması
Temiz Kod kursunun Doğru Yapıyı Seçmek dersinde uzun bir koşul zinciri bir tabloya taşınmıştı;
tablo, tür adını davranışa eşleyen bir kayıt idi. Tasarım İlkeleri kursunun Açık–Kapalı
İlkesi dersinde aynı yapı yeniden çıktı: tarife türü switch ile çözülmek yerine tarifeler
kendilerini bir kayda yazdı, hesap modülü tür adlarını hiç bilmedi.
İki dersin problemi aynı değildi. Birincisi bir gövdenin karar noktası sayısını düşürmek istiyordu, ikincisi yeni bir tür eklendiğinde var olan bir dosyanın düzenlenmesini engellemek. Buna karşın bulunan çözümün yapısı harfi harfine aynıydı: bir eşleme tablosu, tabloya yazan bir işlev, tablodan okuyan bir işlev ve tabloyu dolduran tek bir bileşim kökü. Aynı çözümün ayrı baskılar altında ikinci kez bulunması, o çözümün adlandırılmayı hak ettiğinin işaretidir.
Üçlünün Parçaları
Bir kalıp anlatısı üç bölümden oluşur; her bölüm ayrı bir soruya cevap verir.
| Parça | Sorusu | Kayıt çözümünde karşılığı |
|---|---|---|
| Problem | Hangi baskı altında ortaya çıkar | Tür sayısı artıyor ve her yeni tür var olan dosyaları düzenletiyor |
| Çözüm | Hangi parçalar hangi ilişkiyle durur | Ad–davranış tablosu, yazan işlev, okuyan işlev, dolduran bileşim kökü |
| Sonuçlar | Neyi iyileştirir, neyi kötüleştirir | Tür adının geçtiği yer sayısı düşer; dosya ve ithal kenarı sayısı artar |
Üçlünün önünde bir bağlam cümlesi durur: kalıbın uygulanabilir olduğu koşul. Kayıt çözümünün bağlamı, türlerin çalışma zamanında ada göre seçilmesidir. Türler derleme zamanında belliyse ve seçim hiç yapılmıyorsa problem yoktur, dolayısıyla kalıp da yoktur.
Üç parçadan en çok atlananı sonuçlardır. Bir kalıbı “uygulamak” onun yapısını kurmak değil, sonuçlarını kabul etmektir; bu yüzden sonuçlar sayılmadan kalıp seçimi yapılamaz.
Taşıyıcı Seçiminin İki Sürümü
Kitaplıkta yeni bir problem var: bir gönderi için birden çok taşıyıcı teklifi üretilecek. Her
taşıyıcının kendi ücreti ve kendi teslim süresi var. Birinci sürümde taşıyıcı adı iki ayrı
switch ile çözülür.
// sec/tasiyici.mjs — tasiyici adi iki ayri switch ile cozulur export function ucret(ad, gonderi) { switch (ad) { case "yurtici": return 3900 + Math.ceil(gonderi.agirlik) * 900; case "hizli": return 6400 + Math.ceil(gonderi.agirlik) * 1500; default: throw new RangeError(`bilinmeyen tasiyici: ${ad}`); } } export function sureGun(ad) { switch (ad) { case "yurtici": return 3; case "hizli": return 1; default: throw new RangeError(`bilinmeyen tasiyici: ${ad}`); } }
// sec/teklif.mjs — teklif satiri; tasiyici adlarini kendisi de sayar import { ucret, sureGun } from "./tasiyici.mjs"; export const ADLAR = ["yurtici", "hizli"]; export const satir = (ad, g) => `${ad}: ${ucret(ad, g)} kurus / ${sureGun(ad)} gun`;
// sec/kur.mjs — ornek gonderi icin butun tasiyici tekliflerini yazar import { ADLAR, satir } from "./teklif.mjs"; export const GONDERI = { agirlik: 2.4, adres: "34100" }; for (const ad of ADLAR) console.log(satir(ad, GONDERI));
node sec/kur.mjs
yurtici: 6600 kurus / 3 gun hizli: 10900 kurus / 1 gun
İkinci sürüm kayıt çözümünü uygular. Her taşıyıcı kendi dosyasında bir nesne olarak durur ve kendini kayda yazar.
// kayit/kayit.mjs — tasiyici kaydi: her tasiyici kendini bu tabloya yazar const TASIYICILAR = new Map(); export function kaydet(tasiyici) { TASIYICILAR.set(tasiyici.ad, tasiyici); } export const adlar = () => [...TASIYICILAR.keys()]; export function tasiyici(ad) { const t = TASIYICILAR.get(ad); if (t === undefined) throw new RangeError(`bilinmeyen tasiyici: ${ad}`); return t; }
// kayit/tasiyicilar/yurtici.mjs — yurtici tasiyici import { kaydet } from "../kayit.mjs"; kaydet({ ad: "yurtici", ucret: (g) => 3900 + Math.ceil(g.agirlik) * 900, sureGun: () => 3, });
// kayit/tasiyicilar/hizli.mjs — hizli tasiyici import { kaydet } from "../kayit.mjs"; kaydet({ ad: "hizli", ucret: (g) => 6400 + Math.ceil(g.agirlik) * 1500, sureGun: () => 1, });
// kayit/teklif.mjs — teklif satiri; hicbir tasiyici adi gecmez import { tasiyici } from "./kayit.mjs"; export const satir = (ad, g) => { const t = tasiyici(ad); return `${ad}: ${t.ucret(g)} kurus / ${t.sureGun()} gun`; };
// kayit/kur.mjs — bilesim koku: hangi tasiyicilarin yuklendigini yalniz burasi bilir import "./tasiyicilar/yurtici.mjs"; import "./tasiyicilar/hizli.mjs"; import { adlar } from "./kayit.mjs"; import { satir } from "./teklif.mjs"; export const GONDERI = { agirlik: 2.4, adres: "34100" }; for (const ad of adlar()) console.log(satir(ad, GONDERI));
node kayit/kur.mjs
yurtici: 6600 kurus / 3 gun hizli: 10900 kurus / 1 gun
Çıktılar aynı. Fark yalnızca sonuçlardadır ve sonuçlar sayılabilir.
Sonuçların Sayılması
Aşağıdaki ölçer bir dizindeki modülleri okur ve dört sayı verir: dosya sayısı, ithal kenarı sayısı, verilen tür adlarının dizgi olarak geçtiği yer sayısı ve ithal çizgesindeki en uzun yolun uzunluğu. İlk iki sayı kalıbın bedelini, üçüncüsü kazancını, dördüncüsü dolaylılık basamağını ölçer.
// olc.mjs — bir tasarimin dosya, ithal kenari, tur adi ve dolaylilik olculeri import { readdirSync, readFileSync } from "node:fs"; import { dirname, join, relative, resolve } from "node:path"; const kok = process.argv[2]; const TURLER = process.argv.slice(3); const topla = (d) => readdirSync(d, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? topla(join(d, e.name)) : e.name.endsWith(".mjs") ? [join(d, e.name)] : []); const metin = new Map(topla(kok).sort().map((y) => [relative(kok, y), readFileSync(y, "utf8")])); const kenar = new Map(); for (const [ad, m] of metin) { kenar.set(ad, [...m.matchAll(/(?:from|import)\s*["'](\.[^"']+)["']/g)] .map((x) => relative(kok, resolve(dirname(join(kok, ad)), x[1])))); } const kenarSayisi = [...kenar.values()].reduce((t, h) => t + h.length, 0); let turYeri = 0; for (const [, m] of metin) for (const t of TURLER) turYeri += [...m.matchAll(new RegExp(`"${t}"`, "g"))].length; const derinlik = (ad, gorulen = new Set()) => { const alt = (kenar.get(ad) ?? []).filter((h) => !gorulen.has(h)); return alt.length === 0 ? 0 : 1 + Math.max(...alt.map((h) => derinlik(h, new Set([...gorulen, ad])))); }; const enUzun = Math.max(...[...kenar.keys()].map((a) => derinlik(a))); console.log(`${kok.padEnd(6)} dosya=${metin.size} ithal-kenari=${kenarSayisi} ` + `tur-adi-gecen-yer=${turYeri} en-uzun-ithal-yolu=${enUzun}`);
node olc.mjs sec yurtici hizli node olc.mjs kayit yurtici hizli
sec dosya=3 ithal-kenari=2 tur-adi-gecen-yer=6 en-uzun-ithal-yolu=2 kayit dosya=5 ithal-kenari=7 tur-adi-gecen-yer=2 en-uzun-ithal-yolu=2
Kazanç üçüncü sayıda. Birinci sürümde iki taşıyıcı adı altı yerde geçiyor: her ad iki
switch içinde birer kez ve ADLAR dizisinde bir kez. Bu, Temiz Kod kursunda tanımlanan
bilgi yinelemesidir; taşıyıcı listesi üç ayrı yerde ayrı ayrı tutulur ve üçünün eşit
kalması hiçbir denetimle güvence altında değildir. İkinci sürümde her ad bir kez, yalnız
kendi dosyasında geçer.
Bedel ilk iki sayıda. Dosya sayısı üçten beşe, ithal kenarı sayısı ikiden yediye çıktı. İkinci sayı, Bileşen Bağlaşım İlkeleri dersinde sayılan bağ sayısının aynısıdır: çizgeye beş kenar eklendi ve bunların dördü tek bir dosyaya, bileşim köküne bağlıdır.
Dördüncü sayı kalıbın bedelinin nerede görünmediğini gösteriyor: en uzun ithal yolu iki
sürümde de 2. Yani dolaylılık, dosyalar arasındaki yol uzunluğunda değil, çalışma zamanındaki
çözümlemede artıyor. Birinci sürümde ucret çağrısının hangi gövdeye gittiği dosyayı okuyarak
bulunur; ikinci sürümde çağrının hedefi kayda hangi dosyaların yazdığına bağlıdır ve bu bilgi
yalnız bileşim kökünde durur. Tek bir ölçü kalıbın bedelini vermeye yetmez.
Problem Sınıflarına Göre Bölünme
Kalıplar çözdükleri problem türüne göre ayrılır ve bu kurs bölümlerini o ayrımdan alır. Yaratımsal kalıplar bir nesnenin kim tarafından ve nasıl kurulacağı sorusunu çözer. Yapısal kalıplar var olan nesnelerin nasıl birleştirileceğini düzenler. Davranışsal kalıplar nesneler arasındaki sorumluluk ve iletişim dağılımını ele alır. Kurumsal uygulama kalıpları iş mantığı ile kalıcılık arasındaki sınıra aittir; bunların bir kısmı Backend Geliştirme müfredatının Veri Erişim Katmanı kursunda kurulmuştu ve burada kalıp kataloğu düzeyinde yeniden ele alınır.
Kayıt çözümü bu sınıflamada yaratımsal tarafa düşer, çünkü çözdüğü şey davranışın kendisi değil, davranışı taşıyan nesnenin nereden geleceğidir. Sonraki iki ders bu soruyu daha keskin biçimde sorar.
Özet
- Kalıp bir kod parçası değil, tekrar eden bir problemin adlandırılmış çözümüdür; anlatısı problem, çözüm ve sonuçlar üçlüsünden oluşur ve önünde bir bağlam koşulu durur.
- Bir çözümün kalıp sayılması, aynı yapının ayrı baskılar altında yeniden bulunmasına bağlıdır; kayıt çözümü Temiz Kod ve Tasarım İlkeleri kurslarında iki ayrı problem için birbirinin aynı biçimde çıktı.
- Aynı teklif satırını üreten iki sürümde tür adının geçtiği yer sayısı 6’dan 2’ye düştü; bu kalıbın kazancıdır.
- Aynı karşılaştırmada dosya sayısı 3’ten 5’e, ithal kenarı sayısı 2’den 7’ye çıktı; bu kalıbın bedelidir ve kazançla aynı ölçü kümesinden okunur.
- En uzun ithal yolu iki sürümde de 2 kaldı: bir kalıbın dolaylılık bedeli tek bir ölçüyle görünmez, çağrı hedefinin çalışma zamanında çözülmesi ayrıca hesaba katılır.
Sonraki Adım
Kayıt sürümünde teklif modülü taşıyıcı adlarını bilmiyor, ama hangi adın hangi nesneye karşılık geldiğini yine bir dizgi üzerinden soruyor ve nesnenin kendisini kayıt kuruyor. Nesne kurma işi tek bir çağrıya sığmadığında — taşıyıcının bir ücret hesabı, bir rota planlayıcısı ve bir etiket biçimi varsa — kayda yazılacak şeyin kendisi bir kurulum işlemine dönüşür. Sonraki ders bu kurulumu bir yönteme çeker: alt tipin hangi nesneyi üreteceğine kendisinin karar verdiği fabrika yöntemini tanımlar, birbirine uyması gereken nesneleri birlikte üreten soyut fabrikayı ekler ve yeni bir taşıyıcı ailesi eklendiğinde düzenlenen dosya ve satır sayısını iki sürümde sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.