Kotlin Academy · Oppitunti

Sealed-hierarkioiden sisäkkäisyys ja yhdistäminen

Rakentakaa monimutkaisia tilakoneita sisäkkäisten sealed-tyyppien avulla.

Oppitunti 4/413 vaihetta

Sealed-hierarkioiden sisäkkäisyys ja yhdistäminen on ilmainen Kotlin Academy-oppitunti CoddyKitissä. Tämä on oppitunti 4/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu Kotlin Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Kotlin Academy-kurssilla on yhteensä 4 oppituntia.

Tilapuiden koostaminen

Tosielämän tiloilla on usein useita ulottuvuuksia. Sisäkkäiset sealed-tyypit tai erillisten hierarkioiden yhdistäminen auttavat mallintamaan monimutkaiset toimialueet selkeästi.

Sisäkkäinen sealed-luokka

Määritelkää alisteiset sealed-tyypit ulomman sealed-tyypin sisälle. Kääntäjä valvoo edelleen täydellisyyttä koko puun alueella.

sealed class UiState {
    object Loading : UiState()
    sealed class Loaded : UiState() {
        data class Success(val items: List<String>) : Loaded()
        data class Empty(val message: String) : Loaded()
    }
    data class Error(val msg: String) : UiState()
}
fun render(s: UiState) = when (s) {
    UiState.Loading -> "loading"
    is UiState.Loaded.Success -> "${s.items.size} items"
    is UiState.Loaded.Empty -> "empty: ${s.message}"
    is UiState.Error -> "err: ${s.msg}"
}
fun main() {
    println(render(UiState.Loaded.Success(listOf("a", "b"))))
}

Sealed-rajapinnat mixineille

Käyttäkää sealed-rajapintoja, kun tilalla on useita toisistaan riippumattomia ominaisuuksia – kukin ominaisuus toteutetaan eri rajapintana.

sealed interface Authenticated
data class User(val id: Int) : Authenticated
object Guest
sealed interface Permission
object Read : Permission
object Write : Permission
fun describe(a: Authenticated, p: Permission): String = when {
    a is User && p is Write -> "user ${a.id} can write"
    a is User -> "user ${a.id} can read"
    else -> "?"
}
fun main() {
    println(describe(User(1), Write))
}

Hierarkioiden yhdistäminen

Välittäkää kaksi sealed-arvoa ja käsitelkää niiden yhdistelmää – käyttäkää ehtoja sisältävää when-lauseketta.

sealed class Theme { object Light : Theme(); object Dark : Theme() }
sealed class Lang { object En : Lang(); object Tr : Lang() }
fun greet(theme: Theme, lang: Lang) = when {
    theme is Theme.Dark && lang is Lang.Tr -> "Karanlik Merhaba"
    theme is Theme.Light && lang is Lang.Tr -> "Aydinlik Merhaba"
    theme is Theme.Dark -> "Dark Hello"
    theme is Theme.Light -> "Light Hello"
    else -> "?"
}
fun main() {
    println(greet(Theme.Dark, Lang.Tr))
}

Tilakone sisäkkäisillä muunnoksilla

Mallintakaa monivaiheiset työnkulut sisäkkäisinä sealed-tyyppeinä – kullakin vaiheella on omat muunnoksensa.

sealed class Checkout {
    object Start : Checkout()
    sealed class Payment : Checkout() {
        object Pending : Payment()
        data class Failed(val reason: String) : Payment()
        object Captured : Payment()
    }
    object Shipped : Checkout()
    object Delivered : Checkout()
}
fun describe(c: Checkout) = when (c) {
    Checkout.Start -> "starting"
    Checkout.Payment.Pending -> "awaiting payment"
    is Checkout.Payment.Failed -> "payment failed: ${c.reason}"
    Checkout.Payment.Captured -> "paid"
    Checkout.Shipped -> "shipped"
    Checkout.Delivered -> "delivered"
}
fun main() {
    println(describe(Checkout.Payment.Failed("card declined")))
}

Sealed-hierarkioiden uudelleenkäyttö moduulien välillä

Sealed-tyypit kannattaa määritellä moduulikohtaisesti. Moduulien välisessä koostamisessa paljastakaa sealed interface, jotta kukin moduuli voi lisätä omat toteutuksensa.

sealed interface Notification
data class Email(val to: String) : Notification
data class Sms(val phone: String) : Notification
fun send(n: Notification) = when (n) {
    is Email -> "sending email to ${n.to}"
    is Sms -> "texting ${n.phone}"
}
fun main() {
    println(send(Email("a@example.com")))
}

Yhteiset kantaluokan ominaisuudet

Sealed-luokat voivat määritellä yhteisiä ominaisuuksia, jotka kaikki alityypit perivät. Näin tila ja metatiedot pysyvät DRY-periaatteen mukaisina.

sealed class HttpResponse(val code: Int) {
    class Ok(val body: String) : HttpResponse(200)
    class NotFound(val path: String) : HttpResponse(404)
    class ServerError(val cause: String) : HttpResponse(500)
}
fun describe(r: HttpResponse) = "[${r.code}] " + when (r) {
    is HttpResponse.Ok -> r.body
    is HttpResponse.NotFound -> "not found: ${r.path}"
    is HttpResponse.ServerError -> "fail: ${r.cause}"
}
fun main() {
    println(describe(HttpResponse.Ok("hello")))
}

Rekursiivisten puiden mallintaminen

Sealed-tyypit kuvaavat luontevasti rekursiivisia rakenteita, kuten lausekepuita.

sealed class Expr {
    data class Num(val value: Int) : Expr()
    data class Add(val left: Expr, val right: Expr) : Expr()
    data class Mul(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int = when (e) {
    is Expr.Num -> e.value
    is Expr.Add -> eval(e.left) + eval(e.right)
    is Expr.Mul -> eval(e.left) * eval(e.right)
}
fun main() {
    val expr = Expr.Add(Expr.Num(3), Expr.Mul(Expr.Num(4), Expr.Num(5)))
    println(eval(expr)) // 23
}

Geneeristen tyyppien yhdistäminen

Sealed-tyypit voivat olla geneerisiä, mikä mahdollistaa tyypitetyt tuloskuoret, kuten Result<T>.

sealed class ApiResult<out T> {
    data class Success<T>(val data: T) : ApiResult<T>()
    data class Failure(val message: String) : ApiResult<Nothing>()
}
fun handle(r: ApiResult<String>) = when (r) {
    is ApiResult.Success -> "got ${r.data}"
    is ApiResult.Failure -> "err: ${r.message}"
}
fun main() {
    println(handle(ApiResult.Success("hi")))
    println(handle(ApiResult.Failure("nope")))
}

Vastaesimerkki: liian syvä sisäkkäisyys

Syvä sisäkkäisyys peittää tarkoituksen. Jos sealed-puussa on yli kaksi tasoa, harkitkaa sen litistämistä tai alipuujen erottamista omiin tiedostoihinsa.

sealed class Top {
    sealed class Mid : Top() {
        sealed class Deep : Mid() {
            object Leaf : Deep()
        }
    }
}
fun main() {
    val l = Top.Mid.Deep.Leaf
    println(l::class.simpleName)
}

Yhdistäminen koostamalla

Kun kaksi tilaulottuvuutta ovat toisistaan riippumattomia, suosikaa koostamista (data class, joka sisältää kaksi sealed-arvoa) syvästi sisäkkäisten hierarkioiden sijaan.

sealed class Loading { object Active : Loading(); object Idle : Loading() }
sealed class Auth { data class Signed(val id: Int) : Auth(); object Anon : Auth() }
data class AppState(val loading: Loading, val auth: Auth)
fun main() {
    val s = AppState(Loading.Active, Auth.Signed(1))
    println(s)
}

Pikatarkistus

Mitä suositellaan, kun kaksi tilaulottuvuutta ovat toisistaan riippumattomia?

Kertaus

Sisäkkäistäkää sealed-tyyppejä puumaista tilaa varten ja yhdistäkää toisistaan riippumattomat sealed-hierarkiat koostamalla (käärivällä data classilla). Lisätkää yhteiset ominaisuudet sealed-kantaluokkaan, käyttäkää geneerisiä tyyppejä tyypitettyihin tuloksiin ja mallintakaa rekursiiviset rakenteet luontevasti. Pitäkää sisäkkäisyys matalana.

Aloita maksutta

Opi Kotlin tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
51
Oppitunnit
203

Usein kysytyt kysymykset

Onko oppitunti ”Sealed-hierarkioiden sisäkkäisyys ja yhdistäminen” ilmainen?

Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa Kotlin Academy-oppimispolun 3 oppituntia, myös oppitunnin “Sealed-hierarkioiden sisäkkäisyys ja yhdistäminen”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Kotlin Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Sealed-hierarkioiden sisäkkäisyys ja yhdistäminen”?

Rakentakaa monimutkaisia tilakoneita sisäkkäisten sealed-tyyppien avulla. Harjoittelet Kotlin Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni Kotlin Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin Kotlin Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 4/4.

Kuinka kauan ”Sealed-hierarkioiden sisäkkäisyys ja yhdistäminen”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä Kotlin Academy-oppitunnilla?

Kyllä. Jokainen Kotlin Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. sealed class ja sealed interface: milloin kumpaakin käytetään
  2. Kattava when sealed-hierarkioilla
  3. Käyttöliittymän tilan mallintaminen sealed-luokilla
  4. Sealed-hierarkioiden sisäkkäisyys ja yhdistäminen
← Takaisin: Kotlin Academy