İçeriğe geç
academia.sh

Ders 05 / 14

Tek Yönlü Veri Akışı

Tek yönlü veri akışında olay yukarı çıkar, durum aşağı iner ve arayüz durumu doğrudan yazmaz. Ders, olayı işleyen bir indirgeyicinin kurduğu yeni durumun okunduğunda yanıtı hangi basamağın verdiğini ölçer.

Yayın Güncelleme

Bu derste

  • Tek yönlü veri akışının olay, durum tutucu, yeni durum ve arayüz döngüsünü açıklar
  • Olayı işleyen indirgeyicinin yeni durumu bellekte kurduğunu ve okumanın bellekten geldiğini ölçer
  • Olay işlenmeden yapılan okumanın varsayılandan geldiğini gösterir
  • Olay sırası değişince aynı okumanın değerinin değiştiğini ama kökeninin değişmediğini sınıflar
İçindekiler

Önceki derste arayüz, değişen değeri bir akıştan topladı. Değerin akışa nasıl girdiği ayrı bir sorudur: kim yazıyor? Tek yönlü veri akışında (unidirectional data flow) arayüz durumu yazmaz. Kullanıcının dokunuşu bir olaya (event) dönüşür, olay durum tutucuya gider, tutucu yeni durumu kurar, arayüz yeni durumu okur. Soru yine 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 }}")
    }
}

Döngü: Olay Yukarı, Durum Aşağı

Platformda olay çoğunlukla kapalı bir tür olarak yazılır ve görünüm modeli onu tek bir giriş işleviyle alır. Giriş işlevi mevcut durumdan yeni durumu üretir ve akışa yazar. Arayüz yalnız iki şey yapar: durumu çizer ve olayı gönderir.

// Platform kodu: CartViewModel
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update

sealed interface CartEvent {
    data class QuantityChanged(val quantity: Int) : CartEvent
    object Cleared : CartEvent
}

class CartViewModel : ViewModel() {
    private val _state = MutableStateFlow(CartUiState())
    val state: StateFlow<CartUiState> = _state

    fun onEvent(event: CartEvent) {
        _state.update { current ->
            when (event) {
                is CartEvent.QuantityChanged -> current.copy(quantity = event.quantity)
                CartEvent.Cleared -> CartUiState()
            }
        }
    }
}
// Platform kodu: CartScreen
@Composable
fun CartScreen(viewModel: CartViewModel) {
    val state by viewModel.state.collectAsState()
    Button(onClick = { viewModel.onEvent(CartEvent.QuantityChanged(state.quantity + 1)) }) {
        Text("Adet: ${state.quantity}")
    }
}

Bu iki 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.

M31/K03’te arayüzü bir ağaç olarak kurmuş ve verinin o ağaçta hangi yöne gittiğini ölçmüştük. Burada soru ağacın yönü değildir: aşağı inen durumun okunduğunda yanıtı hangi basamağın verdiğini soruyoruz. Yön, bu kursta kategori değildir; okumanın kökeni ölçülür.

Modelde indirgeyici (reducer) iki girdi alan bir işlevdir: mevcut durum ve olay. Dönüşü, olayın değerleriyle üstü yazılmış yeni durumdur. applyAll olayları sırayla işler. stateReads yeni durumu bellek basamağına koyar ve dört okuma kurar.

// Reducer.kt — olay mevcut durumdan yeni durumu uretir
class Event(val name: String, val values: Map<String, String>)

fun reduce(state: Map<String, String>, event: Event): Map<String, String> =
    state + event.values

fun applyAll(initial: Map<String, String>, events: List<Event>): Map<String, String> =
    events.fold(initial) { current, event -> reduce(current, event) }

fun stateReads(state: Map<String, String>): List<Read> {
    val chain = listOf(Tier("bellek", state))
    return listOf(
        Read("adet", "sepet.adet", chain, "0"),
        Read("toplam", "sepet.toplam", chain, "0"),
        Read("indirim", "sepet.indirim", chain, "yok"),
        Read("not", "sepet.not", chain, "bos"),
    )
}

Olay İşlenir, Durum Bellekte Kurulur

Sepet bir adetle başlasın. Kullanıcı adedi ikiye çıkarır, ardından toplam hesaplanır. İki olay indirgeyiciden geçer ve yeni durum okunur.

// Main.kt — iki olay islenir, yeni durum bellekte kurulur
fun main() {
    val initial = mapOf("sepet.adet" to "1")
    val events = listOf(
        Event("adet-ayarla", mapOf("sepet.adet" to "2")),
        Event("toplam-hesapla", mapOf("sepet.toplam" to "240")),
    )
    val state = applyAll(initial, events)
    val reads = stateReads(state)
    reads.forEach { it.show() }
    countOrigins(reads)
}
adet: anahtar=sepet.adet | deger=2 -> bellek
toplam: anahtar=sepet.toplam | deger=240 -> bellek
indirim: anahtar=sepet.indirim | deger=yok -> varsayilan
not: anahtar=sepet.not | deger=bos -> varsayilan
bellek: 2
disk: 0
ag: 0
varsayilan: 2

Adet ve toplam, indirgeyicinin kurduğu durumdan okundu: iki okuma bellek. İndirim ve not hiçbir olayla yazılmadı; zincirin tek basamağı bellek olduğu için onlar varsayılana düştü. Yani durumun yeni kurulmuş olması okumayı bellek yapar, ama kurulmayan alan yine varsayılandır.

UM1. Model durumu düz bir harita olarak kurar ve arayüzün olay göndermesini bir liste elemanına indirger. Platformda olay zamanla gelir ve akış her değişiklikte arayüzü yeniden çizer; modelde çizim yoktur.

Olay Sırası Değişir, Köken Değişmez

Aynı iki olay ters sırada gelirse ne olur? Önce adet beşe, sonra ikiye ayarlansın; ardından aynı olaylar ters sırada işlensin. İndirgeyici üstüne yazdığı için son olay kazanır.

// Main.kt — olay sirasi degeri degistirir, koken ayni kalir
fun main() {
    val first = Event("adet-bes", mapOf("sepet.adet" to "5"))
    val second = Event("adet-iki", mapOf("sepet.adet" to "2"))

    val inOrder = stateReads(applyAll(mapOf(), listOf(first, second)))
    val reversed = stateReads(applyAll(mapOf(), listOf(second, first)))

    println("sira: bes, iki")
    inOrder[0].show()
    println("sira: iki, bes")
    reversed[0].show()
}
sira: bes, iki
adet: anahtar=sepet.adet | deger=2 -> bellek
sira: iki, bes
adet: anahtar=sepet.adet | deger=5 -> bellek

Aynı okuma iki değer verdi: 2 ve 5. İkisi de bellekten geldi. Kural değeri değil basamağı görür: sıra değişince değer değişti, köken değişmedi.

UM2. Model “son olay kazanır” kuralını üstüne yazma ile kurar. Platformda aynı alana yazan iki olay birleştirilebilir, sıralanabilir ya da atılabilir; bu karar uygulamaya aittir ve modelde yoktur.

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

Sınır, olayın henüz işlenmediği an. Durum boşken okunan alan, olay işlendikten sonra okunan aynı alanla karşılaştırılır.

// Main.kt — olay islenmeden okuma varsayilan verir
fun main() {
    val before = stateReads(mapOf())
    println("olay islenmeden:")
    countOrigins(before)

    val after = stateReads(
        applyAll(
            mapOf(),
            listOf(
                Event("adet-ayarla", mapOf("sepet.adet" to "2")),
                Event("indirim-ekle", mapOf("sepet.indirim" to "kod-10")),
            ),
        )
    )
    println("olaylar islenince:")
    after.forEach { it.show() }
    countOrigins(after)
}
olay islenmeden:
bellek: 0
disk: 0
ag: 0
varsayilan: 4
olaylar islenince:
adet: anahtar=sepet.adet | deger=2 -> bellek
toplam: anahtar=sepet.toplam | deger=0 -> varsayilan
indirim: anahtar=sepet.indirim | deger=kod-10 -> bellek
not: anahtar=sepet.not | deger=bos -> varsayilan
bellek: 2
disk: 0
ag: 0
varsayilan: 2

Olay işlenmeden dört okumanın dördü de varsayılan verdi. İki olaydan sonra adet ve indirim bellekten geldi; toplam ve not hâlâ varsayılan. Durumu yazan olaydır; olay gelmedikçe arayüzün okuyacağı şey zincirde değil, kodun yazdığı varsayılandır.

UM3. Model boş durumu boş haritayla kurar. Platformda ilk durum çoğunlukla yüklemeden önce gelen bir ilk değerdir; Gözlemlenebilir Durum dersinde gördüğümüz gibi.

Özet

  • Bu dersin kökeni: bellek. Olayı işleyen indirgeyici yeni durumu bellekte kurdu ve iki olaydan sonra adet ve toplam bellek basamağından okundu.
  • Dersin ölçütü, okumanın yeni kurulan durumdan mı yoksa hiç yazılmamış bir alandan mı geldiği. Olay işlenmeden bellek 0, varsayılan 4; iki olaydan sonra bellek 2, varsayılan 2 çıktı.
  • Olay sırası değişince aynı okuma 2 ve 5 verdi, ama ikisinin kökeni de bellek: kural değeri değil basamağı görür.
  • Arayüz durumu yazmaz; durumu yazan olaydır. Olay gelmeyen alan varsayılan kalır.
  • Yön bu kursta kategori değildir; ölçülen, yanıtı hangi basamağın verdiğidir.

Sonraki Adım

Şimdiye kadar zincirin basamaklarını biz kurduk. Gerçek uygulamada zinciri kim kurar? Sıradaki ders, Bağımlılık Enjeksiyonu, bir nesnenin ihtiyaç duyduğu basamakların dışarıdan nasıl verildiğini ve üretim zincirinde yanıtın nereden geldiğini ö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