Ders 04 / 30
Prototip
Nesneyi kurmak yerine var olan bir nesneyi kopyalayarak yaratma: türetilmiş tarifelerin alanlarını elle yazan sürüm ile prototipi klonlayan sürümün karşılaştırılması, prototipe yeni bir alan eklendiğinde eksik alan sayısının ölçülmesi, sığ kopyanın paylaştığı alt nesne sayısının sayılması ve türetme zincirinin sessiz değişiklik olarak geri dönen bedeli.
İçindekiler
Kurucu her nesneyi sıfırdan kurar. Kitaplıkta kurulacak nesnelerin çoğu ise birbirinin küçük bir sapmasıdır: sözleşmeli müşteri tarifesi standart tarifenin iki alanı değişmiş hâli, bölgesel tarife bir bölge katsayısı değişmiş hâli, kurumsal tarife de sözleşmeli tarifenin bir alanı değişmiş hâli. Bu nesneleri adım adım kurmak, aynı alanları defalarca yazmak demektir.
Prototip (prototype) kalıbı yaratımı kurulumdan kopyalamaya çevirir: yeni nesne, var olan bir nesnenin kopyası alınıp üzerinde birkaç alan değiştirilerek elde edilir. Bu dersin iki ölçüsü var. Birincisi kazanç: prototipe yeni bir alan eklendiğinde türetilmiş nesnelerde kaç alan eksik kalıyor. İkincisi bedel: aynı bağın istenmeyen bir değişikliği kaç nesneye taşıdığı ve kopyanın kaç alt nesneyi prototiple paylaştığı.
Elle Yazılan Türetilmiş Tarifeler
Birinci sürümde dört tarife de bağımsızdır. Her biri beş alanını kendi metninde taşır.
// elle/tarife.mjs — her turetilmis tarife butun alanlarini yeniden yazar export const standart = { ad: "standart", kademeler: [[1, 3900], [5, 6400], [20, 11800]], bolge: { "34": 100, "06": 115, "65": 140 }, asgariUcret: 3900, sigortaOrani: 0.004, }; export const sozlesmeli = { ad: "sozlesmeli", kademeler: [[1, 3900], [5, 6400], [20, 11800]], bolge: { "34": 100, "06": 115, "65": 140 }, asgariUcret: 3200, sigortaOrani: 0.003, }; export const bolgesel = { ad: "bolgesel", kademeler: [[1, 3900], [5, 6400], [20, 11800]], bolge: { "34": 100, "06": 115, "65": 125 }, asgariUcret: 3900, sigortaOrani: 0.004, }; export const kurumsal = { ad: "kurumsal", kademeler: [[1, 3900], [5, 6400], [20, 11800]], bolge: { "34": 100, "06": 115, "65": 140 }, asgariUcret: 3200, sigortaOrani: 0.002, };
Kademe tablosu dört kez, bölge tablosu üç kez aynı biçimde yazılmıştır. Bu, Temiz Kod kursunda tanımlanan bilgi yinelemesidir: dört nesnenin kademe tablosunun eşit kalması hiçbir düzenekle güvence altında değildir.
Prototipten Klonlama
İkinci sürümde yalnız standart tarife tam yazılır. Diğerleri tureti çağrısıyla elde edilir:
prototip klonlanır, ada ve türetildiği prototipin adına yazılır, sonra istenen alanlar üzerine
geçirilir.
// prototip/tarife.mjs — turetilmis tarifeler prototipi klonlayip alan degistirir export const standart = { ad: "standart", kademeler: [[1, 3900], [5, 6400], [20, 11800]], bolge: { "34": 100, "06": 115, "65": 140 }, asgariUcret: 3900, sigortaOrani: 0.004, }; export function tureti(prototip, ad, degisiklik) { const kopya = structuredClone(prototip); return Object.assign(kopya, { ad, tureten: prototip.ad }, degisiklik); } export const sozlesmeli = tureti(standart, "sozlesmeli", { asgariUcret: 3200, sigortaOrani: 0.003 }); export const bolgesel = tureti(standart, "bolgesel", { bolge: { ...standart.bolge, "65": 125 } }); export const kurumsal = tureti(sozlesmeli, "kurumsal", { sigortaOrani: 0.002 });
Son satır kalıbın ayırt edici yanını gösteriyor: kurumsal tarife standart tarifeden değil,
sözleşmeli tarifeden türer. Prototip bir sınıf değil, çalışan bir nesnedir; dolayısıyla
türetilmiş bir nesne de kendisi prototip olabilir. Bu düzeneğin JavaScript’in kendi nesne
modelindeki [[Prototype]] bağıyla ilgisi yoktur; JavaScript Nesne Modeli kursunda ele alınan
o bağ arama zinciri kurar, buradaki klon ise bağımsız bir nesnedir ve alanlarını kopya olarak
taşır.
Aşağıdaki betik verilen dizindeki dört tarifeyle aynı gönderinin ücretini hesaplar. İki sürümün eşdeğerliği bununla sınanır.
// fiyat.mjs — verilen dizindeki dort tarifeyle ayni gonderinin ucreti const dizin = process.argv[2]; const t = await import(`./${dizin}/tarife.mjs`); const G = { agirlik: 2.4, il: "65", deger: 180000 }; const ucret = (tf) => { const taban = tf.kademeler.find(([ust]) => G.agirlik <= ust)?.[1] ?? 15000; const ham = Math.round((taban * (tf.bolge[G.il] ?? 160)) / 100) + Math.round(G.deger * tf.sigortaOrani); return Math.max(ham, tf.asgariUcret); }; for (const ad of ["standart", "sozlesmeli", "bolgesel", "kurumsal"]) console.log(`${dizin.padEnd(9)} ${ad.padEnd(11)} ${ucret(t[ad])} kurus`);
node fiyat.mjs elle node fiyat.mjs prototip
elle standart 9680 kurus elle sozlesmeli 9500 kurus elle bolgesel 8720 kurus elle kurumsal 9320 kurus prototip standart 9680 kurus prototip sozlesmeli 9500 kurus prototip bolgesel 8720 kurus prototip kurumsal 9320 kurus
Dört ücret de aynı. Fark yalnız türetilmiş nesnelerin nasıl elde edildiğinde.
Sığ Kopya ile Derin Kopya
Kalıbın ilk tuzağı kopyanın derinliğidir. Nesne yayma ({ ...prototip }) alanları birer birer
kopyalar, ama alan bir nesneye ya da diziye başvuruyorsa kopyalanan şey başvurunun kendisidir.
Aşağıdaki ölçer prototipin iki alt nesnesi için başvuru eşitliğini sayar, sonra her iki kopyada
bir kademe değerini değiştirip prototipe ne olduğunu yazar.
// paylasim.mjs — sig ve derin kopyanin prototiple paylastigi alt nesne sayisi import { standart } from "./prototip/tarife.mjs"; const ALT = ["kademeler", "bolge"]; const paylasilan = (k) => ALT.filter((a) => k[a] === standart[a]).length; const sig = { ...standart, ad: "sig-kopya" }; const derin = structuredClone(standart); derin.ad = "derin-kopya"; console.log(`sig paylasilan-alt-nesne=${paylasilan(sig)}`); console.log(`derin paylasilan-alt-nesne=${paylasilan(derin)}`); derin.kademeler[0][1] = 4200; console.log(`derin kademesi degisti -> standart ilk kademe=${standart.kademeler[0][1]}`); sig.kademeler[0][1] = 4200; console.log(`sig kademesi degisti -> standart ilk kademe=${standart.kademeler[0][1]}`);
node paylasim.mjs
sig paylasilan-alt-nesne=2 derin paylasilan-alt-nesne=0 derin kademesi degisti -> standart ilk kademe=3900 sig kademesi degisti -> standart ilk kademe=4200
Sığ kopya iki alt nesneyi prototiple paylaşıyor. Sonucu son satırda: sığ kopyanın kademesine yazmak prototipin kademesini de değiştirdi. Bu, Bağlaşım Türleri dersinde ölçülen ortak bağlaşımın aynısıdır — iki nesnenin davranışı ortak değiştirilebilir bir duruma bağlıdır — ve kalıbın yanlış uygulandığı en yaygın biçimdir. Derin kopyada paylaşılan alt nesne sayısı sıfır olduğu için prototip değişmedi.
Prototipe Eklenen Alanın İzlenmesi
Kalıbın kazancı prototip değiştiğinde ortaya çıkar. Aşağıdaki ölçer türetilmiş üç tarifede tanımsız kalan alanları sayar, sigorta oranlarını listeler ve türetme zincirinin derinliğini verir.
// alan-say.mjs — turetilmis tarifelerde eksik alan, sigorta orani ve prototip zinciri const dizin = process.argv[2]; const t = await import(`./${dizin}/tarife.mjs`); const ALAN = ["ad", "kademeler", "bolge", "asgariUcret", "sigortaOrani", "yakitEki"]; const TURETILMIS = ["sozlesmeli", "bolgesel", "kurumsal"]; const eksik = TURETILMIS.reduce( (toplam, ad) => toplam + ALAN.filter((a) => t[ad][a] === undefined).length, 0); const oran = TURETILMIS.map((ad) => t[ad].sigortaOrani).join(","); const zincir = (ad) => (t[ad].tureten === undefined ? 0 : 1 + zincir(t[ad].tureten)); const derinlik = Math.max(...TURETILMIS.map(zincir)); console.log(`${dizin.padEnd(13)} eksik-alan=${eksik} sigorta-orani=${oran} ` + `zincir-derinligi=${derinlik}`);
Değişiklik tek bir istektir: standart tarifeye yakıt eki alanı eklenir ve sigorta oranı yükseltilir. Aşağıdaki betik bu düzenlemeyi iki ağacın kopyalarına birebir aynı biçimde uygular; yalnız standart tarifenin alanına dokunduğundan emin olmak için metni ad üzerinden konumlar.
// yama.mjs — standart tarifenin sigorta oranini yukseltir ve yakit eki alani ekler import { readFileSync, writeFileSync } from "node:fs"; const yol = process.argv[2]; const metin = readFileSync(yol, "utf8"); const bas = metin.indexOf('ad: "standart"'); const alan = metin.indexOf("sigortaOrani:", bas); const satirSonu = metin.indexOf("\n", alan); writeFileSync(yol, `${metin.slice(0, alan)}sigortaOrani: 0.006,\n yakitEki: 250,${metin.slice(satirSonu)}`);
node alan-say.mjs elle node alan-say.mjs prototip cp -r elle elle-yeni cp -r prototip prototip-yeni node yama.mjs elle-yeni/tarife.mjs node yama.mjs prototip-yeni/tarife.mjs node alan-say.mjs elle-yeni node alan-say.mjs prototip-yeni node fiyat.mjs elle-yeni node fiyat.mjs prototip-yeni
elle eksik-alan=3 sigorta-orani=0.003,0.004,0.002 zincir-derinligi=0 prototip eksik-alan=3 sigorta-orani=0.003,0.004,0.002 zincir-derinligi=2 elle-yeni eksik-alan=3 sigorta-orani=0.003,0.004,0.002 zincir-derinligi=0 prototip-yeni eksik-alan=0 sigorta-orani=0.003,0.006,0.002 zincir-derinligi=2 elle-yeni standart 10040 kurus elle-yeni sozlesmeli 9500 kurus elle-yeni bolgesel 8720 kurus elle-yeni kurumsal 9320 kurus prototip-yeni standart 10040 kurus prototip-yeni sozlesmeli 9500 kurus prototip-yeni bolgesel 9080 kurus prototip-yeni kurumsal 9320 kurus
Kazanç ilk sütunda. Değişiklikten önce iki sürümde de üç alan eksikti; yakıt eki hiçbir tarifede yoktu. Düzenlemeden sonra elle yazılan sürümde eksik alan sayısı 3’te kaldı: yeni alan yalnız standart tarifeye girdi, türetilmiş üç tarife onu hiç görmedi. Klonlayan sürümde sayı 0’a indi, çünkü türetilmiş tarifeler yükleme anında prototipten üretilir.
Bedel ikinci ve üçüncü sütunda. Sigorta oranı sözleşmeli ve kurumsal tarifelerde üzerine yazıldığı için değişmedi; bölgesel tarifede yazılmamıştı ve 0.004’ten 0.006’ya çıktı. Ücret satırlarında karşılığı görünüyor: bölgesel tarifenin ücreti elle yazılan sürümde 8720 kuruşta kaldı, klonlayan sürümde 9080 kuruşa çıktı. Bu değişiklik hiçbir yerde istenmemiştir; yalnız prototipe dokunulmuştur.
Üçüncü sütun bu bedelin neden zor fark edildiğini söylüyor. Türetme zinciri derinliği 2’dir: kurumsal tarife sözleşmeli tarifeden, o da standart tarifeden türer. Bir alanın nihai değerini bulmak için zinciri geriye doğru izlemek ve her basamakta alanın üzerine yazılıp yazılmadığına bakmak gerekir. Elle yazılan sürümde bu derinlik 0’dır: her tarifenin bütün alanları kendi metnindedir.
İki sayının aynı düzeneğin iki yüzü olduğuna dikkat edilmelidir. Yeni alanın kendiliğinden taşınması ile istenmeyen değişikliğin kendiliğinden taşınması aynı bağdır. Kalıp bu yüzden türetilmiş nesnelerin gerçekten prototiple birlikte değişmesi gerektiği durumlarda uygulanır; bağımsız değişmesi gereken nesneler için taşınan değişiklik kazanç değil kusurdur.
Özet
- Prototip kalıbı yeni nesneyi kurulum adımlarıyla değil, var olan bir nesnenin kopyası üzerinden üretir; prototip bir sınıf değil çalışan bir nesnedir, dolayısıyla türetilmiş bir nesne de prototip olabilir.
- Prototipe yeni bir alan eklendiğinde elle yazılan sürümde eksik alan sayısı 3’te kaldı, klonlayan sürümde 0’a indi.
- Aynı düzenleme bölgesel tarifenin sigorta oranını 0.004’ten 0.006’ya taşıdı ve ücretini 8720 kuruştan 9080 kuruşa çıkardı; bu değişiklik istenmemişti.
- Sığ kopya prototiple 2 alt nesne paylaştı ve kopyanın kademesine yazmak prototipin kademesini de değiştirdi; derin kopyada paylaşılan alt nesne sayısı 0’dır.
- Türetme zinciri derinliği klonlayan sürümde 2, elle yazılan sürümde 0’dır; bir alanın nihai değeri ancak zincir izlenerek bulunur.
Sonraki Adım
Dört yaratımsal düzenek de nesnenin nereden geldiğini düzenledi, ama hepsinde nesne çağıran tarafından elde tutuldu. Bir sonraki soru nesnenin kaç tane olduğudur. Kitaplıkta tarife kaydı gibi tek bir örneğinin bulunması istenen nesneler var; bu isteği karşılamanın en kısa yolu nesneyi modül düzeyinde kurup her yerden aynı örneğe erişmektir. Sonraki ders bunun sınanabilirlik cinsinden bedelini ölçer: aynı sınama kümesi tekil örnek üzerinden koşturulduğunda kaç sınamanın başarısız olduğunu, sıra değiştiğinde sonucun değişip değişmediğini ve durumu sıfırlayan düzeneğin dışa açılan yüzeye kaç ad eklediğini sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.