Zagnieżdżanie i łączenie hierarchii sealed
Buduj złożone automaty stanów, zagnieżdżając typy sealed.
Zagnieżdżanie i łączenie hierarchii sealed to bezpłatna lekcja Kotlin Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Kotlin Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Komponowanie drzew stanów
Stan w rzeczywistych aplikacjach często ma wiele wymiarów. Należy zagnieżdżać typy sealed lub łączyć oddzielne hierarchie, aby przejrzyście modelować złożone domeny.
Zagnieżdżona sealed class
Typy potomne sealed należy deklarować wewnątrz zewnętrznego typu sealed. Kompilator nadal wymusza kompletność w całym drzewie.
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"))))
}Interfejsy sealed jako miksy
Interfejsów sealed należy używać, gdy stan ma wiele niezależnych aspektów — każdy aspekt jest innym interfejsem.
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))
}Łączenie hierarchii
Należy przekazać dwie wartości sealed i przełączać się na podstawie ich kombinacji — używając when z warunkami.
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))
}Maszyna stanów ze zagnieżdżonymi wariantami
Wielostopniowe przepływy pracy należy modelować jako zagnieżdżone typy sealed — każdy krok ma własne warianty.
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")))
}Ponowne używanie hierarchii sealed w modułach
Typy sealed najlepiej definiować w obrębie jednego modułu. W przypadku kompozycji między modułami należy udostępnić sealed interface, aby każdy moduł mógł dodawać własne implementacje.
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")))
}Wspólne właściwości bazowe
Klasy sealed mogą deklarować wspólne właściwości — wszystkie podtypy je dziedziczą. Dzięki temu stan i metadane pozostają zgodne z zasadą DRY.
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")))
}Modelowanie rekurencyjnych drzew
Typy sealed w naturalny sposób opisują struktury rekurencyjne, takie jak drzewa wyrażeń.
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
}Łączenie z generykami
Typy sealed mogą być generyczne, co umożliwia tworzenie typowanych opakowań wyników, takich jak 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")))
}Antywzorzec: zbyt głębokie zagnieżdżanie
Głębokie zagnieżdżanie ukrywa intencję. Jeśli drzewo sealed ma więcej niż 2 poziomy, należy rozważyć jego spłaszczenie lub wyodrębnienie poddrzew do osobnych plików.
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)
}Łączenie za pomocą kompozycji
Gdy dwa wymiary stanu są niezależne, należy preferować kompozycję (klasę data opakowującą dwie wartości sealed) zamiast głęboko zagnieżdżonych hierarchii.
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)
}Szybkie sprawdzenie
Jakie podejście jest zalecane, gdy dwa wymiary stanu są niezależne?
Podsumowanie
Typy sealed należy zagnieżdżać, aby modelować stan w kształcie drzewa, a niezależne hierarchie sealed łączyć za pomocą kompozycji (opakowującej klasy data). Do bazy sealed należy dodawać wspólne właściwości, generyków używać dla typowanych wyników, a struktury rekurencyjne modelować w naturalny sposób. Zagnieżdżanie powinno pozostać płytkie.
Często zadawane pytania
Czy lekcja „Zagnieżdżanie i łączenie hierarchii sealed” jest bezpłatna?
Tak — pełny tekst „Zagnieżdżanie i łączenie hierarchii sealed” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Kotlin Academy, przejdź na CoddyKit PRO. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Zagnieżdżanie i łączenie hierarchii sealed”?
Buduj złożone automaty stanów, zagnieżdżając typy sealed. Ćwiczysz Kotlin Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Kotlin Academy?
Nie wymagamy żadnego doświadczenia. Kotlin Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Zagnieżdżanie i łączenie hierarchii sealed”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Kotlin Academy?
Tak. Każda lekcja Kotlin Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- sealed class a sealed interface: kiedy używać każdej z nich
- Kompletne when z hierarchiami sealed
- Modelowanie stanu interfejsu za pomocą klas sealed
- Zagnieżdżanie i łączenie hierarchii sealed