İçeriğe geç
academia.sh

Ders 06 / 14

Bağımlılık Enjeksiyonu

Bağımlılık enjeksiyonunda bir sınıf ihtiyaç duyduğu nesneleri kendisi kurmaz, dışarıdan alır. Ders, konteynerin kurduğu üretim zincirinde okumanın yanıtını hangi basamağın verdiğini ve testte zincir değişince yanıtın nasıl değiştiğini ölçer.

Yayın Güncelleme

Bu derste

  • Kurucu enjeksiyonunu, konteyneri, modülü ve kapsamı açıklar
  • Konteynerin kurduğu üretim zincirinde bellek ve disk boşken yanıtın ağdan geldiğini ölçer
  • Aynı tüketicinin farklı kapsamlarda aynı kökenden farklı değer okuduğunu gösterir
  • Testte bellek basamağına sahte veri enjekte edilince aynı okumanın bellekten geldiğini sınıflar
İçindekiler

Önceki derslerde zincirin basamaklarını doğrudan biz kurduk. Gerçek uygulamada bu işi bir yapı üstlenir: bağımlılık enjeksiyonu (dependency injection). Bir sınıf ihtiyaç duyduğu nesneleri kendisi oluşturmaz, kurucusunda dışarıdan alır. Nesneleri kuran ve dağıtan yapıya konteyner (container) denir. 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 }}")
    }
}

Kurucudan Verilen Zincir

Kurucu enjeksiyonunda (constructor injection) sınıf bağımlılığını parametre olarak ister. Modül (module) hangi nesnenin nasıl kurulacağını tarif eder; kapsam (scope) o nesnenin ne zamana kadar paylaşılacağını belirler. Enjeksiyon kütüphaneleri bu tarifi derleme sırasında ya da çalışırken işler; biz yalnız kurucu parametresini ve tarifin sonucunu gösteriyoruz.

// Platform kodu: CartRepository ve modül
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Inject
import javax.inject.Singleton

class CartRepository @Inject constructor(
    private val remote: CartApi,
    private val local: CartDao,
)

@Module
@InstallIn(SingletonComponent::class)
object DataModule {
    @Provides
    @Singleton
    fun provideCartApi(): CartApi = CartApi.create()
}

Tüketici CartApi ve CartDao nesnesini kendisi kurmaz; hangisinin geleceğini modül belirler. 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.

Daha önceki kurslardaki iki soruyu burada ayırmak gerekir. M12/K02 bir değişikliğin nereye ulaştığını, M12/K03 bir sonucu kimin değiştirebildiğini sorar. Bu derste kimin hangi nesneyi kurduğunu anlatıyoruz, ama ölçtüğümüz o değildir: kategori nesneyi veren sahip değil, okumada yanıt veren basamaktır. Konteyner zinciri kurmuş olsa da bakılan şey kurucu değildir; yanıtın hangi basamaktan geldiğidir.

Modelde konteyner bir zincirdir. Container.read zinciri bir okumaya verir. preload ağ yanıtından istenen anahtarları belleğe önceden yükler. productionContainer üretim zincirini kurar: belleği preload dönüşüyle kurar, diski boş bırakır, ağı doldurur. testContainer bellek basamağına sahte veri koyar. CartScreenModel tüketicidir ve konteyneri kurucudan alır; hangi zincirin geldiğini bilmez.

// Container.kt — konteyner zinciri kurar, tuketici zinciri kurucudan alir
class Container(private val chain: List<Tier>) {
    fun read(label: String, key: String, fallback: String): Read =
        Read(label, key, chain, fallback)
}

fun preload(network: Map<String, String>, keys: List<String>): Map<String, String> =
    keys.filter { it in network }.associateWith { network.getValue(it) }

fun productionContainer(network: Map<String, String>): Container =
    Container(listOf(Tier("bellek", preload(network, listOf("kampanya.kod"))), Tier("disk", mapOf()), Tier("ag", network)))

fun testContainer(fake: Map<String, String>, network: Map<String, String>): Container =
    Container(listOf(Tier("bellek", fake), Tier("disk", mapOf()), Tier("ag", network)))

class CartScreenModel(private val container: Container) {
    fun readAll(): List<Read> = listOf(
        container.read("baslik", "ekran.baslik", "bos"),
        container.read("urun-fiyati", "urun.fiyat", "0"),
        container.read("stok", "urun.stok", "0"),
        container.read("kampanya", "kampanya.kod", "yok"),
    )
}

Üretim Zinciri

Üretimde konteyner belleğe yalnız kampanya kodunu önceden yüklemek ister ve diski boş bırakır. Ağ yanıtı başlığı, fiyatı ve stoğu taşır; kampanya kodu yok, bu yüzden preload boş bir eşlem döndürür ve bellek de boş kalır.

// Main.kt — uretim zincirinde yanit agdan gelir
fun main() {
    val network = mapOf(
        "ekran.baslik" to "Sepet",
        "urun.fiyat" to "120",
        "urun.stok" to "7",
    )
    val model = CartScreenModel(productionContainer(network))
    val reads = model.readAll()
    reads.forEach { it.show() }
    countOrigins(reads)
}
baslik: anahtar=ekran.baslik | deger=Sepet -> ag
urun-fiyati: anahtar=urun.fiyat | deger=120 -> ag
stok: anahtar=urun.stok | deger=7 -> ag
kampanya: anahtar=kampanya.kod | deger=yok -> varsayilan
bellek: 0
disk: 0
ag: 3
varsayilan: 1

Üç okuma ağdan geldi; kampanya hiçbir basamakta bulunmadığı için varsayılana düştü. Bellek ve disk bu zincirde yanıt vermeyen basamaklardı: boş olduklarından okuma onları geçip ağa ulaştı.

UM1. Model konteyneri düz bir zincir olarak kurar ve kapsamı, zinciri her çağrıda yeniden kurmakla taklit eder. Platformda konteyner nesne grafiğini kurar ve tekil nesneleri yaşatır; zincir bu grafiğin yalnız bir kesitidir.

Kapsam Değişir, Köken Değişmez

Aynı tüketici iki kullanıcının kapsamında çalışsın. Her kapsam kendi ağ yanıtıyla kurulur; tüketici sınıfı ikisinde de aynıdır.

// Main.kt — iki kapsam: ayni tuketici, ayni koken, farkli deger
fun main() {
    val scopes = mapOf(
        "kullanici-a" to mapOf("urun.fiyat" to "120"),
        "kullanici-b" to mapOf("urun.fiyat" to "95"),
    )
    for ((name, network) in scopes) {
        val model = CartScreenModel(productionContainer(network))
        println("kapsam: $name")
        model.readAll()[1].show()
    }
}
kapsam: kullanici-a
urun-fiyati: anahtar=urun.fiyat | deger=120 -> ag
kapsam: kullanici-b
urun-fiyati: anahtar=urun.fiyat | deger=95 -> ag

İki kapsamda fiyat 120 ve 95 çıktı; ikisinin de kökeni ağ. Tüketici aynı kaldı, değer değişti, köken değişmedi. Kapsam değeri belirler; kural yalnız yanıtı kimin verdiğine bakar.

UM2. Model kapsamı bir ağ yanıtı haritası olarak kurar. Platformda kapsam bir nesnenin yaşam sınırıdır ve aynı kapsamdaki iki tüketici aynı örneği paylaşır; modelde nesne paylaşımı yoktur.

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

Test, enjeksiyonun asıl kazancıdır: tüketici değişmeden zincir değişir. Testte bellek basamağına sahte fiyat ve stok konur. Ağ yanıtı üretimdekiyle aynı kalır.

// Main.kt — testte bellek basamagina sahte veri enjekte edilir
fun main() {
    val network = mapOf(
        "ekran.baslik" to "Sepet",
        "urun.fiyat" to "120",
        "urun.stok" to "7",
    )
    val fake = mapOf("urun.fiyat" to "1", "urun.stok" to "1")
    val model = CartScreenModel(testContainer(fake, network))
    val reads = model.readAll()
    reads.forEach { it.show() }
    countOrigins(reads)
}
baslik: anahtar=ekran.baslik | deger=Sepet -> ag
urun-fiyati: anahtar=urun.fiyat | deger=1 -> bellek
stok: anahtar=urun.stok | deger=1 -> bellek
kampanya: anahtar=kampanya.kod | deger=yok -> varsayilan
bellek: 2
disk: 0
ag: 1
varsayilan: 1

Aynı tüketici, aynı ağ yanıtı; ama fiyat ve stok artık bellekten geldi ve değerleri 1 oldu. Başlık yine ağdan, kampanya yine varsayılandan. Sahte veri bellek basamağına girince okumanın yanıtı oradan verildi. Kural basamağı kimin kurduğuna değil, yanıtı kimin verdiğine göre sınıflar.

UM3. Model sahte veriyi bir bellek basamağı olarak kurar. Platformda sahte nesne bir arayüzün test gerçeklemesidir; sahte nesnenin davranışı bu derste ölçülmez.

Özet

  • Bu dersin kökeni: ağ. Konteynerin kurduğu üretim zincirinde bellek ve disk boştu, ağ doluydu; başlık, fiyat ve stok ağdan geldi.
  • Dersin ölçütü, yanıt vermeyen basamakların ardından ağın yanıt vermesi. Üretimde ağ 3, varsayılan 1, bellek 0, disk 0 çıktı.
  • İki kapsamda aynı tüketici 120 ve 95 okudu; ikisi de ağ. Kapsam değeri belirler, kökeni değil.
  • Testte bellek basamağına sahte veri girince fiyat ve stok bellekten geldi: bellek 2, ağ 1, varsayılan 1.
  • Kategori nesneyi kuran sahip değil, yanıtı veren basamaktır.

Sonraki Adım

Zincirin basamaklarını ve onları kuran yapıyı gördük. Bir sonraki kurs konusu, Depo Kalıbı, bu zinciri tek bir arayüzün arkasına toplar: ekran hangi basamaktan okunduğunu bilmeden ister, depo sırayı yönetir ve veri katmanı da aynı ölçüyle okunur.

İ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