İçeriğe geç
academia.sh

Ders 07 / 22

Merkezî Depo Kalıbı

Durumu tek yerde toplayan kalıp; tek yönlü veri akışı, saf indirgeyici, eylem sözleşmesi, seçicilerle gereksiz bildirimin süzülmesi, ara katmanların yeri ve kalıbın dolaylılık bedeli.

İçindekiler

Durum türleri ayrıldı ama paylaşılan durumun nasıl tutulacağı açık değil. Ortak atada tutulan bir değer, ağaç derinleştikçe onu kullanmayan onlarca bileşenin arasından geçirilir. Daha ağır sorun, değişimin dağınıklığıdır: Kuzey Yamaç Ölçüm İstasyonu uygulamasında bir satırın seçilmesi, bir ölçümün silinmesi ve oturumun kapatılması aynı durumu üç ayrı yerden değiştirir. Ekranda yanlış bir değer göründüğünde onu kimin yazdığı izlenemez.

Merkezî depo kalıbı bu iki soruna tek bir kısıtla yanıt verir: durum tek bir yerde tutulur ve yalnız adlandırılmış eylemlerle değişir.

Tek Yönlü Akış

Kalıbın omurgası dört durakta kapanan bir çevrimdir. Görünüm bir eylem gönderir; eylem indirgeyiciye verilir; indirgeyici yeni durumu üretir; yeni durum görünüme akar.

Bu çevrimin dışında durumu değiştirmenin yolu yoktur. Kısıt, kalıbın bütün kazançlarının kaynağıdır: değişimlerin tümü tek bir noktadan geçtiği için sıralanabilir, kaydedilebilir ve yeniden oynatılabilir.

Üç parça ayrı ayrı tanımlanır. Eylem, olmuş bir olayı bildiren veri nesnesidir; bir türü ve gerektiği kadar alanı vardır. İndirgeyici, o anki durumla eylemi alıp yeni durumu döndüren saf bir işlevdir. Depo, o anki durumu tutan, eylem göndermeyi ve değişimi izlemeyi sağlayan kaptır.

İndirgeyici

İndirgeyici saf bir işlevdir: aynı girdi her zaman aynı çıktıyı verir, dışarıda hiçbir şey değiştirmez. Ağ isteği yapmaz, rastgele sayı üretmez, o anki zamanı okumaz.

Durumu değiştirmez, yenisini üretir. Bu, Programlama Temelleri kursundaki değiştirilemezlik kuralının doğrudan uygulanmasıdır ve pratik bir sonucu vardır: bir dilimin referansı değişmediyse o dilim değişmemiştir. Bu ucuz sınama, görünümün ne zaman yeniden çizileceğini belirler.

// merkezi-depo.mjs — indirgeyici, eylem dizisi, ara katman, secici ve yeniden oynatma
const BASLANGIC = { istasyonlar: [], secili: null, suzgec: "hepsi" };

function indirgeyici(durum, eylem) {
  switch (eylem.tur) {
    case "istasyonlar/yuklendi":
      return { ...durum, istasyonlar: eylem.veri };
    case "istasyon/secildi":
      return durum.istasyonlar.some((i) => i.kimlik === eylem.kimlik)
        ? { ...durum, secili: eylem.kimlik }
        : durum;                                   // gecersiz secim durumu degistirmez
    case "istasyon/eklendi":
      return { ...durum, istasyonlar: [...durum.istasyonlar, eylem.istasyon] };
    case "istasyon/silindi":
      return {
        ...durum,
        istasyonlar: durum.istasyonlar.filter((i) => i.kimlik !== eylem.kimlik),
        secili: durum.secili === eylem.kimlik ? null : durum.secili,
      };
    case "suzgec/degisti":
      return { ...durum, suzgec: eylem.deger };
    default:
      return durum;                                // bilinmeyen eylem: ayni referans
  }
}

function depoKur(indirgeyici, baslangic, araKatmanlar = []) {
  let durum = baslangic;
  const dinleyiciler = new Set();
  const temel = (eylem) => {
    durum = indirgeyici(durum, eylem);
    for (const d of dinleyiciler) d(durum);
  };
  const gonder = araKatmanlar.reduceRight(
    (sonraki, katman) => katman({ oku: () => durum })(sonraki), temel);
  return { oku: () => durum, gonder, izle: (f) => dinleyiciler.add(f) };
}

const gunluk = [];
const kutukKatmani = ({ oku }) => (sonraki) => (eylem) => {
  const onceki = oku();
  sonraki(eylem);
  gunluk.push({ eylem: eylem.tur, degisti: oku() !== onceki, durum: oku() });
};

const EYLEMLER = [
  { tur: "istasyonlar/yuklendi", veri: [{ kimlik: "kuzey-yamac" }, { kimlik: "dogu-sirt" }] },
  { tur: "istasyon/secildi", kimlik: "kuzey-yamac" },
  { tur: "suzgec/degisti", deger: "sicaklik" },
  { tur: "istasyon/secildi", kimlik: "bati-vadi" },     // listede yok
  { tur: "istasyon/eklendi", istasyon: { kimlik: "bati-vadi" } },
  { tur: "istasyon/secildi", kimlik: "bati-vadi" },
  { tur: "istasyon/silindi", kimlik: "bati-vadi" },
  { tur: "tema/degisti", deger: "koyu" },               // indirgeyici tanimiyor
];

const depo = depoKur(indirgeyici, BASLANGIC, [kutukKatmani]);

// Secici: bilesenin ilgilendigi dilim. Referans degismediyse yeniden cizim gerekmez.
const seciciSayaci = { bildirim: 0, listeDegisti: 0 };
let oncekiListe = depo.oku().istasyonlar;
depo.izle((d) => {
  seciciSayaci.bildirim += 1;
  if (d.istasyonlar !== oncekiListe) { seciciSayaci.listeDegisti += 1; oncekiListe = d.istasyonlar; }
});

for (const e of EYLEMLER) depo.gonder(e);

console.log("-- eylem gunlugu --");
console.log("#".padEnd(3), "eylem".padEnd(24), "degisti".padEnd(8), "istasyon", "secili");
gunluk.forEach((g, i) => console.log(
  String(i + 1).padEnd(3), g.eylem.padEnd(24), String(g.degisti).padEnd(8),
  String(g.durum.istasyonlar.length).padEnd(8), String(g.durum.secili)));

console.log("-- dinleyici --");
console.log("gonderilen eylem:", EYLEMLER.length,
  "| bildirim:", seciciSayaci.bildirim,
  "| liste dilimi degisti:", seciciSayaci.listeDegisti);

// Indirgeyici saf oldugu icin ayni dizi ayni sonucu verir.
console.log("-- yeniden oynatma --");
const yeniden = EYLEMLER.reduce(indirgeyici, BASLANGIC);
console.log("son durum ayni mi:", JSON.stringify(yeniden) === JSON.stringify(depo.oku()));
console.log("3. eylemden sonrasi:", JSON.stringify(EYLEMLER.slice(0, 3).reduce(indirgeyici, BASLANGIC)));
console.log("son durum          :", JSON.stringify(depo.oku()));
-- eylem gunlugu --
#   eylem                    degisti  istasyon secili
1   istasyonlar/yuklendi     true     2        null
2   istasyon/secildi         true     2        kuzey-yamac
3   suzgec/degisti           true     2        kuzey-yamac
4   istasyon/secildi         false    2        kuzey-yamac
5   istasyon/eklendi         true     3        kuzey-yamac
6   istasyon/secildi         true     3        bati-vadi
7   istasyon/silindi         true     2        null
8   tema/degisti             false    2        null
-- dinleyici --
gonderilen eylem: 8 | bildirim: 8 | liste dilimi degisti: 3
-- yeniden oynatma --
son durum ayni mi: true
3. eylemden sonrasi: {"istasyonlar":[{"kimlik":"kuzey-yamac"},{"kimlik":"dogu-sirt"}],"secili":"kuzey-yamac","suzgec":"sicaklik"}
son durum          : {"istasyonlar":[{"kimlik":"kuzey-yamac"},{"kimlik":"dogu-sirt"}],"secili":null,"suzgec":"sicaklik"}

Günlüğün dördüncü ve sekizinci satırları aynı davranışı iki nedenle gösterir. Listede bulunmayan bir istasyonun seçilmesi reddedilir; indirgeyici o anki durumu aynı referansla geri döndürür. Tanınmayan eylem türü de aynı biçimde ele alınır. Her iki durumda da hiçbir görünümün yeniden çizilmesi gerekmez ve bunu anlamak için tek bir referans karşılaştırması yeter.

Yedinci satır tek eylemin birden çok dilimi etkileyebildiğini gösterir. Seçili istasyon silindiğinde hem liste kısalır hem de seçim boşaltılır; bu iki değişiklik ayrı eylemlere bölünmez, çünkü aralarında ekranın tutarsız kalacağı bir an oluşmamalıdır. Değişmez şudur: seçili kimlik ya boştur ya da listede bulunur.

Yeniden Oynatma

Son bölüm saflığın karşılığını gösterir. Eylem dizisi baştan indirgendiğinde depoda oluşan durumun aynısı elde edilir. Depoya ihtiyaç bile yoktur; durum, başlangıç değeri ile eylem dizisinin bir fonksiyonudur.

Bunun üç kullanımı vardır. Kullanıcıdan gelen bir hata bildirimi eylem günlüğüyle birlikte alındığında sorun yerel olarak yeniden üretilebilir. Herhangi bir ana dönmek, o ana kadarki eylemleri yeniden indirgemekle olur — çıktıda üçüncü eylemden sonraki durum böyle üretilmiştir. Ve geri alma işlevi, silinen eylemi çıkarıp diziyi yeniden indirgeyerek kurulur.

Bu olanaklar indirgeyicinin saflığına bağlıdır. İçine o anki zamanı okuyan tek bir satır konduğunda yeniden oynatma farklı sonuç verir ve üçü birden kaybolur.

Seçici

Dinleyici bölümü kalıbın en sık atlanan maliyetini sayıyla verir: sekiz eylem gönderilmiş, sekiz bildirim üretilmiş, ama istasyon listesi yalnız üç kez değişmiştir.

Depo, durumun hangi bölümünün değiştiğini bilmez; her gönderimde tüm dinleyicilere haber verir. Bileşenlerin bu bildirimleri süzmesi gerekir. Seçici, durumdan bileşenin ilgilendiği dilimi çıkaran işlevdir; dilimin referansı değişmediyse yeniden çizim yapılmaz.

Seçicinin bir tuzağı vardır. Her çağrıda yeni bir nesne ya da dizi üreten seçici — süzme, eşleme veya nesne toplama yapan bir seçici — referans karşılaştırmasını her zaman başarısız kılar ve süzgeç hiç çalışmaz. Bu tür seçiciler girdileri değişmediği sürece aynı sonucu döndürecek biçimde bellenir.

Ara Katman

İndirgeyici saf olmak zorundaysa, ağ isteği, kütüğe yazma ve depolamaya kaydetme nereye konur? Ara katman, eylem gönderme yolunun üzerine yerleşen ve eylemi indirgeyiciye ulaşmadan önce ya da ulaştıktan sonra işleyen sarmalayıcıdır.

Örnekteki kütük katmanı eylemi geçirir, sonra öncesi ve sonrasındaki durumu karşılaştırıp kaydeder. Aynı yerde başka sorumluluklar da durur: bir eylemi eşzamansız bir işleme çevirmek, belirli eylemlerden sonra durumu depolamaya yazmak, gelen eylemi doğrulamak.

Ara katmanların sırası anlamlıdır: dıştaki katman içtekini sarar. Kütük katmanı en dışta durursa eşzamansız katmanın ürettiği eylemleri de görür; en içte durursa yalnız indirgeyiciye ulaşanları.

Kalıbın Bedeli

Merkezî depo bedelsiz değildir. Bir alanı değiştirmek için eylem türü tanımlanır, indirgeyiciye dal eklenir, gönderme yazılır; üç dosyaya dokunulur. Bu dolaylılık tek bir şey karşılığında alınır: değişimin izlenebilirliği. İzlenebilirliğin değeri olmadığı yerde bedel de gereksizdir.

Buradan iki sınır çıkar. Yerel durum depoya konmaz; bir açılır listenin açıklığı için eylem tanımlamak, kazanç getirmeyen bir dolaylılıktır. Sunucu durumu da konmaz — kopyalanabilir ama tazelik, yeniden getirme ve istek tekrarı soruları indirgeyicinin çözemeyeceği sorulardır; o katman bu konunun dördüncü dersinin konusudur.

Depoya giren şey, uygulamanın kendi ürettiği ve birden çok yerden değiştirilen paylaşılan durumdur.

Özet

  • Merkezî depo kalıbı durumu tek yerde toplar ve değişimi yalnız adlandırılmış eylemlerle kabul eder; akış görünüm, eylem, indirgeyici ve durum arasında tek yönde kapanır.
  • İndirgeyici saf bir işlevdir ve durumu değiştirmeden yenisini üretir; değişmeyen dilimin referansı korunur, bu da yeniden çizim kararını tek bir karşılaştırmaya indirir.
  • Saflık, eylem dizisinden durumu yeniden üretmeyi sağlar: hata yeniden oynatılabilir, geçmiş bir ana dönülebilir, geri alma kurulabilir.
  • Depo her gönderimde tüm dinleyicilere haber verir; gereksiz çizimi seçiciler süzer ve yeni nesne üreten seçiciler bellenmezse süzgeç çalışmaz.
  • Yan etkiler ara katmanlarda durur; katman sırası hangi eylemlerin görüleceğini belirler.
  • Kalıbın bedeli dolaylılıktır; yerel durum ve sunucu durumu depoya konmaz.

Sonraki Adım

Merkezî depo tek bir doğru kaynak kurdu, ama bunun bir bedeli çıktı: her değişiklik tek bir durum nesnesini yeniler ve tüm dinleyicilere duyurulur. Gereksiz çizim, doğru yazılmış seçicilerle önlenir — yani doğruluk, her tüketicinin kendi süzgecini kusursuz kurmasına bağlıdır. Bu sorumluluğun ters çevrildiği bir yaklaşım daha vardır: durumu tek bir büyük nesne olarak değil, birbirine bağlı küçük birimler olarak tanımlamak. O zaman kimin neyi okuduğu bağımlılıktan kendiliğinden çıkar ve yalnız etkilenen tüketiciler haberdar edilir. Sonraki ders bu ince taneli modeli, atomları ve türetilmiş atomları ele alı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