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.