İçeriğe geç
academia.sh

Ders 03 / 14

Durum Tutucular

Durum tutucu, bir ekranın durumunu tek yerde toplar; kaydedilmiş durum süreç sonlansa da dönen alanları seçer. Ders bir ekranın alanlarının hangi kökenden, hangi kaydedilen alanlarla geri geldiğini ölçer.

Yayın Güncelleme

Bu derste

  • Arayüz durumunun tek doğruluk kaynağı olarak bir durum tutucuda toplandığını açıklar
  • Durum tutucuyu görünüm modelinden ve düz sınıftan ayırır
  • Süreç sonlanmasından sonra kaydedilen alanların diskten geri geldiğini ölçer
  • Kaydedilmeyen alanın varsayılana ya da ağa düştüğünü, kaydedilen alan sayısıyla kökenlerin değiştiğini gösterir
İçindekiler

Önceki derste ekran döndüğünde bellek korundu, süreç sonlandığında boşaldı ve kaydedilmemiş sekme seçimi varsayılana düştü. Bu derste ekranın durumunu tek bir yerde topluyoruz ve hangi alanlarının süreç sonlandıktan sonra geri döneceğini seçiyoruz. Soru aynı: bu okumanın yanıtı nereden geliyor?

Ölçü aracı önceki derslerin çekirdeğidir; değişmeden tekrar veriyoruz.

// Origin.kt — bir okumanin yanitinin hangi basamaktan geldigini siniflandirir
class Tier(val origin: String, private val entries: Map<String, String>) {
    init {
        require(origin in listOf("bellek", "disk", "ag")) { "gecersiz basamak: $origin" }
    }

    fun lookup(key: String): String? = entries[key]
}

class Read(val label: String, val key: String, chain: List<Tier>, fallback: String) {
    private val hit: Tier? = chain.firstOrNull { it.lookup(key) != null }
    val value: String = hit?.lookup(key) ?: fallback
    val origin: String = hit?.origin ?: "varsayilan"

    fun show() {
        println("$label: anahtar=$key | deger=$value -> $origin")
    }
}

fun countOrigins(reads: List<Read>) {
    for (name in listOf("bellek", "disk", "ag", "varsayilan")) {
        println("$name: ${reads.count { it.origin == name }}")
    }
}

Tek Doğruluk Kaynağı

Bir ekranın durumu birkaç alandan oluşur: sepetteki adet, arama kutusundaki metin, açık sekme, sıralama. Alanlar arayüz nesnelerinde dağınık dururlarsa aynı bilginin birden çok kopyası olur ve kopyalar ayrışabilir. Çözüm her alanın yazılabilir tek bir sahibi olmasıdır; buna tek doğruluk kaynağı (single source of truth) denir. Arayüz bu sahibi okur, alanı kendi kopyasında tutmaz.

Durumu ve onu değiştiren mantığı taşıyan sınıfa durum tutucu (state holder) denir. Önceki dersin görünüm modeli bir durum tutucudur, ama durum tutucu olmak için görünüm modeli olmak gerekmez. Düz bir sınıf da durum tutucu olabilir; fark, onun ömrünü kimin belirlediğidir.

// Platform kodu: CartStateHolder
data class CartState(val quantity: Int = 1, val filter: String = "")

class CartStateHolder {
    var state = CartState()
        private set

    fun setQuantity(value: Int) {
        state = state.copy(quantity = value)
    }
}

CartStateHolder durumu state alanında tutar ve yalnız kendisi yazar. Bu parça platform kodudur ve burada çıktı, süre ya da dosya yolu iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Kaydedilen Alanlar

Süreç sonlandığında bellekteki durum tutucu gider. Geri dönecek alanlar için platform kaydedilmiş durum (saved state) sunar: durum tutucu hangi alanlarını kaydedeceğini kendisi seçer.

// Platform kodu: CartViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel

class CartViewModel(private val handle: SavedStateHandle) : ViewModel() {
    val filter: String = handle["filter"] ?: ""

    fun setFilter(value: String) {
        handle["filter"] = value
    }
}

filter anahtarı kaydedilmiş duruma yazılır ve süreç sonlandıktan sonra yeni görünüm modeline geri verilir. Kaydedilmeyen alan yalnız bellekte kalır.

Modelde durum tutucu alanları ve kaydedilecek anahtarlar kümesini taşır. memory anlık alanları verir; snapshot yalnız kaydedilenleri süzer. screenReads ekranın dört okumasını kurar.

// Holder.kt — durum tutucu: ekran alanlari tek yerde, kaydedilecek olanlar ayri isaretli
class Holder(private val fields: Map<String, String>, private val saved: Set<String>) {
    fun memory(): Map<String, String> = fields.toMap()
    fun snapshot(): Map<String, String> = fields.filterKeys { it in saved }
}

fun screenReads(chain: List<Tier>): List<Read> = listOf(
    Read("sepet-adedi", "sepet.adet", chain, "0"),
    Read("filtre-metni", "filtre.metin", chain, "yok"),
    Read("ekran-sekmesi", "ekran.sekme", chain, "ilk"),
    Read("siralama", "siralama", chain, "ad"),
)

Önce süreç sonlanmadan önceki hâl. Durum tutucunun dört alanı da bellektedir.

// Main.kt — durum tutucu ekran alanlarini tek yerde tutar
fun main() {
    val holder = Holder(
        mapOf("sepet.adet" to "3", "filtre.metin" to "kulaklik", "ekran.sekme" to "ozet", "siralama" to "fiyat"),
        setOf("filtre.metin", "ekran.sekme"),
    )
    println("kaydedilecek anahtarlar: ${holder.snapshot().keys.sorted()}")
    val reads = screenReads(listOf(Tier("bellek", holder.memory())))
    reads.forEach { it.show() }
    countOrigins(reads)
}
kaydedilecek anahtarlar: [ekran.sekme, filtre.metin]
sepet-adedi: anahtar=sepet.adet | deger=3 -> bellek
filtre-metni: anahtar=filtre.metin | deger=kulaklik -> bellek
ekran-sekmesi: anahtar=ekran.sekme | deger=ozet -> bellek
siralama: anahtar=siralama | deger=fiyat -> bellek
bellek: 4
disk: 0
ag: 0
varsayilan: 0

Dört okuma bellekten geldi. İki anahtar kaydedilecek olarak işaretli: arama metni ve açık sekme. Sepet adedi ağda zaten durduğu için, sıralama ise yalnız arayüz tercihi olduğu için işaretli değil.

Süreç Sonlanınca

Süreç sonlandı. Bellek boş; yeni durum tutucu kaydedilmiş durumu alıyor. Modelde kaydedilmiş durumu disk basamağı olarak kuruyoruz: süreç sonlansa da kalan yer. Ağda yalnız sepet adedi var.

// Main.kt — surec sonlandi, kaydedilmis durum diskten geri gelir
fun main() {
    val network = mapOf("sepet.adet" to "3")
    val holder = Holder(
        mapOf("sepet.adet" to "3", "filtre.metin" to "kulaklik", "ekran.sekme" to "ozet", "siralama" to "fiyat"),
        setOf("filtre.metin", "ekran.sekme"),
    )
    val chain = listOf(Tier("bellek", emptyMap()), Tier("disk", holder.snapshot()), Tier("ag", network))
    val reads = screenReads(chain)
    reads.forEach { it.show() }
    countOrigins(reads)
}
sepet-adedi: anahtar=sepet.adet | deger=3 -> ag
filtre-metni: anahtar=filtre.metin | deger=kulaklik -> disk
ekran-sekmesi: anahtar=ekran.sekme | deger=ozet -> disk
siralama: anahtar=siralama | deger=ad -> varsayilan
bellek: 0
disk: 2
ag: 1
varsayilan: 1

Arama metni ve sekme diskten geri geldi. Adet kaydedilmediği için ağdan okundu. Sıralama hiçbir basamakta olmadığı için varsayılana düştü. Aynı ekranda aynı anda üç köken: disk 2, ağ 1, varsayılan 1.

UM1. Model kaydedilmiş durumu disk basamağı olarak kurar. Platformda bu paketin nerede saklandığını model söylemez; modelin ayırdığı tek şey, paketin süreç sonlanmasından sağ çıkmasıdır.

UM2. Model kaydedilecek anahtarlar kümesini durum tutucuya elle verir. Platformda kaydedilen değerin küçük ve dizileştirilebilir olması gerekir; modelde bu kısıt yok.

Sınırlayıcı Ölçüm

Kaydedilmeyen alan varsayılana ya da ağa düşer. Peki kaydedilen küme değişince kökenler nasıl değişir? Aynı alanlarla üç küme deneyelim: hiçbiri, iki alan, hepsi.

// Main.kt — kaydedilen alan kumesi kokenlerin dagilimini degistirir
fun main() {
    val network = mapOf("sepet.adet" to "3")
    val fields = mapOf("sepet.adet" to "3", "filtre.metin" to "kulaklik", "ekran.sekme" to "ozet", "siralama" to "fiyat")
    val savedSets = listOf(
        "hicbiri" to emptySet<String>(),
        "iki-alan" to setOf("filtre.metin", "ekran.sekme"),
        "hepsi" to fields.keys,
    )
    for ((name, saved) in savedSets) {
        val snapshot = Holder(fields, saved).snapshot()
        val chain = listOf(Tier("bellek", emptyMap()), Tier("disk", snapshot), Tier("ag", network))
        println("kaydedilen: $name")
        countOrigins(screenReads(chain))
    }
}
kaydedilen: hicbiri
bellek: 0
disk: 0
ag: 1
varsayilan: 3
kaydedilen: iki-alan
bellek: 0
disk: 2
ag: 1
varsayilan: 1
kaydedilen: hepsi
bellek: 0
disk: 4
ag: 0
varsayilan: 0

Hiçbir alan kaydedilmediğinde disk sıfırdır: adet ağdan gelir, üç alan varsayılana düşer. İki alan kaydedildiğinde önceki ölçümün dağılımı çıkar. Hepsi kaydedildiğinde dört okumanın dördü diskten gelir ve ağa gidilmez. Ekranı yeniden kuran sistem aynı, değişen tek şey durum tutucunun hangi alanları işaretlediğidir.

UM3. Model kaydetmeyi alanın anahtarıyla işaretler. Platformda kaydetmek her yazmada yapılır; modelde kaydedilen küme sabittir ve anlık güncellenmez.

Özet

  • Bu dersin kökeni: disk. Süreç sonlandıktan sonra kaydedilen arama metni ve sekme seçimi kaydedilmiş durumdan, modelde disk basamağından geri geldi.
  • Dersin ölçütü, süreç sonlandıktan sonra bir alanın hangi kökenden geri geldiği. Aynı ekranda dört okuma disk 2, ağ 1, varsayılan 1 verdi.
  • Durum tutucu alanları tek yerde toplar; sürece sağ çıkacak olanları kaydedilen anahtarlar kümesi belirler.
  • Kaydedilen küme boşken disk 0, ağ 1, varsayılan 3; hepsi kaydedilince disk 4 çıktı.
  • Kaydedilmeyen alan bellekle birlikte gider; ağda ya da varsayılanda yeniden kurulur.

Sonraki Adım

Bu derste durum tutucunun alanları süreç sonlandıktan sonra geri geldi, ama tutucu henüz yüklemenin bitip bitmediğini bilmiyor. Sıradaki ders, Gözlemlenebilir Durum, durum akışının ilk değeri ile yüklenen değer arasındaki farkı ölçer.

İ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