kotlinintermediate

Sealed Interfaces — Flexible Hierarchies

Use sealed interfaces for multi-hierarchy modeling: states, results, commands, and event systems.

kotlin
// Sealed interface allows multiple inheritance
sealed interface UIEvent
sealed interface Loggable {
    val logMessage: String
}

// Events can implement multiple sealed interfaces
data class Click(val x: Int, val y: Int) : UIEvent, Loggable {
    override val logMessage = "Click at ($x, $y)"
}

data class KeyPress(val key: Char) : UIEvent, Loggable {
    override val logMessage = "Key pressed: $key"
}

data class Scroll(val delta: Int) : UIEvent {
    // Not Loggable
}

// State machine with sealed interface
sealed interface ConnectionState {
    data object Disconnected : ConnectionState
    data object Connecting : ConnectionState
    data class Connected(val sessionId: String) : ConnectionState
    data class Error(val reason: String) : ConnectionState
    data class Reconnecting(val attempt: Int, val maxAttempts: Int) : ConnectionState
}

class ConnectionManager {
    var state: ConnectionState = ConnectionState.Disconnected
        private set

    fun connect() {
        state = when (state) {
            is ConnectionState.Disconnected,
            is ConnectionState.Error -> {
                println("Connecting...")
                ConnectionState.Connecting
            }
            else -> {
                println("Already connecting/connected")
                state
            }
        }
    }

    fun onConnected(sessionId: String) {
        state = ConnectionState.Connected(sessionId)
        println("Connected: $sessionId")
    }

    fun onError(reason: String) {
        state = ConnectionState.Error(reason)
        println("Error: $reason")
    }

    fun statusMessage(): String = when (val s = state) {
        ConnectionState.Disconnected -> "Not connected"
        ConnectionState.Connecting -> "Connecting..."
        is ConnectionState.Connected -> "Connected (${s.sessionId})"
        is ConnectionState.Error -> "Error: ${s.reason}"
        is ConnectionState.Reconnecting -> "Reconnecting (${s.attempt}/${s.maxAttempts})"
    }
}

// Command pattern with sealed interface
sealed interface Command {
    data class CreateUser(val name: String, val email: String) : Command
    data class UpdateUser(val id: String, val name: String) : Command
    data class DeleteUser(val id: String) : Command
    data class SendEmail(val to: String, val subject: String, val body: String) : Command
}

sealed interface CommandResult {
    data class Success(val message: String) : CommandResult
    data class Failure(val error: String) : CommandResult
}

fun executeCommand(cmd: Command): CommandResult = when (cmd) {
    is Command.CreateUser -> CommandResult.Success("Created user: ${cmd.name}")
    is Command.UpdateUser -> CommandResult.Success("Updated user: ${cmd.id}")
    is Command.DeleteUser -> CommandResult.Success("Deleted user: ${cmd.id}")
    is Command.SendEmail -> CommandResult.Success("Sent email to: ${cmd.to}")
}

// Sealed interface for JSON values
sealed interface JsonValue {
    data class JsonString(val value: String) : JsonValue
    data class JsonNumber(val value: Double) : JsonValue
    data class JsonBool(val value: Boolean) : JsonValue
    data class JsonArray(val items: List<JsonValue>) : JsonValue
    data class JsonObject(val fields: Map<String, JsonValue>) : JsonValue
    data object JsonNull : JsonValue

    fun stringify(): String = when (this) {
        is JsonString -> "\"$value\""
        is JsonNumber -> if (value == value.toLong().toDouble()) value.toLong().toString() else value.toString()
        is JsonBool -> value.toString()
        is JsonArray -> items.joinToString(", ", "[", "]") { it.stringify() }
        is JsonObject -> fields.entries.joinToString(", ", "{", "}") {
            "\"${it.key}\": ${it.value.stringify()}"
        }
        JsonNull -> "null"
    }
}

fun main() {
    // Events
    val events: List<UIEvent> = listOf(
        Click(100, 200),
        KeyPress('A'),
        Scroll(-3)
    )

    events.forEach { event ->
        print("Event: ${event::class.simpleName}")
        if (event is Loggable) print(" [${event.logMessage}]")
        println()
    }

    // Connection state machine
    println("\n--- Connection ---")
    val conn = ConnectionManager()
    println(conn.statusMessage())
    conn.connect()
    println(conn.statusMessage())
    conn.onConnected("sess-123")
    println(conn.statusMessage())

    // Commands
    println("\n--- Commands ---")
    val commands = listOf(
        Command.CreateUser("Alice", "alice@test.com"),
        Command.SendEmail("bob@test.com", "Hello", "World")
    )
    commands.forEach { cmd ->
        val result = executeCommand(cmd)
        println("$result")
    }

    // JSON values
    println("\n--- JSON ---")
    val json = JsonValue.JsonObject(mapOf(
        "name" to JsonValue.JsonString("Alice"),
        "age" to JsonValue.JsonNumber(30.0),
        "active" to JsonValue.JsonBool(true),
        "tags" to JsonValue.JsonArray(listOf(
            JsonValue.JsonString("kotlin"),
            JsonValue.JsonString("dev")
        )),
        "address" to JsonValue.JsonNull
    ))
    println(json.stringify())
}

Use Cases

  • Multi-interface state modeling
  • Command and event pattern implementation
  • Type-safe AST and data representation

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.