En este tutorial verás cómo declarar clases sealed en Kotlin con el fin de restringir la creación de subclases de una clase.
El Modificador sealed
Marca una clase con el modificador sealed
para que restrinja su jerarquía y especificar al compilador que solo existirán determinados descendientes y ninguno más.
Considera la declaración de clase de ejemplo Padre
junto a sus descendientes HijoA
, HijoB
e HijoC
.
sealed class Padre
class HijoA : Padre()
class HijoB : Padre()
class HijoC : Padre()
Al marcar a Padre
con el modificador sealed
no te será posible crear más subclases de esta clase por fuera del archivo.
Por eso al español podríamos referirnos a esta naturaleza como clases selladas, un adjetivo que representa hermetismo sobre la herencia.
Internamente las clases sealed en Kotlin:
- Son clases abstractas
- Tienen constructores son privados por defecto
- Tienen subclases que deben declararse en el mismo archivo Kotlin
Ejemplo De Clases sealed En Kotlin
Tomemos como ilustración el diseño de un videojuego, donde se han modelado unidades través de la clase base GameUnit
y dos subclases para aldeanos (Villager
) y milicias (Militia
).
open class GameUnit(var hitPoints: Int)
data class Villager(var health: Int = 25) : GameUnit(health)
data class Militia(var health: Int = 40, var armor: Int = 2) : GameUnit(health)
GameUnit
establece los puntos de vida de cada unidad con hitPoints
. La clase Villager
proporciona un valor por defecto a los puntos de vida de 25.
Militia
posee 40 puntos junto a una propiedad armor
para representar los puntos de armadura contra cualquier tipo de daño.
Si quisieras calcular el daño causado a nuestras unidades podríamos crear una función que aplique sentencias de reducción de hitPoints
junto a la expresión when:
private fun damage(unit: GameUnit, attack: Int): String {
return when (unit) {
is Villager -> {
unit.hitPoints -= attack
"El aldeano recibe $attack daños. Vida: ${unit.hitPoints}"
}
is Militia -> {
val damage = max(0, attack - unit.armor)
unit.hitPoints -= damage
"La milicia recibe $damage daños. Vida: ${unit.hitPoints}"
}
else -> throw IllegalArgumentException("Unidad desconocidad")
}
}
En el caso de los aldeanos el daño causado es plano, por lo que el ataque es recibido al 100%. Para la milicia la reducción es la resta entre el daño y los puntos de armadura, si la armadura es mayor al daño, entonces recibe 0 daños.
Con este código podrás simular los daños causados a varias unidades a disposición del jugador:
fun main() {
// Unidades
val units = listOf(
Villager(), Militia(), Villager()
)
// Las unidades reciben daños
units.forEach {
val attack = Random.nextInt(1, 10)
println(damage(it, attack))
}
}
Salida:
El aldeano recibe 8 daños. Vida: 17
La milicia recibe 2 daños. Vida: 38
El aldeano recibe 4 daños. Vida: 21
Sin embargo, nuestra función puede ser reescrita de forma más corta como verás a continuación.
Expresión when con Clases sealed
Ya que la expresión when
en el ejemplo anterior no es exhaustiva, el uso de return
nos obliga a usar la rama para else
con el fin de cubrir los demás casos.
else -> throw IllegalArgumentException("Unidad desconocidad")
Sin embargo, sabemos que no existen más casos. Y es justo allí donde el modificador sealed
viene de maravilla para el diseño.
Si reemplazamos a open
por sealed
en GameUnit
de la siguiente forma:
sealed class GameUnit(var hitPoints: Int)
Inmediatamente verás que IntelliJ IDEA muestra en gris el uso de la rama else
, indicándote que la implementación es redundante.
Esto significa que el uso de las clases sealed
le permite al compilador de Kotlin dar por terminada las comprobaciones de las posibles subclases.
private fun damage(unit: GameUnit, attack: Int): String {
return when (unit) {
is Villager -> {
unit.hitPoints -= attack
"El aldeano recibe $attack daños. Vida: ${unit.hitPoints}"
}
is Militia -> {
val damage = max(0, attack - unit.armor)
unit.hitPoints -= damage
"La milicia recibe $damage daños. Vida: ${unit.hitPoints}"
}
}
}
De esta forma la restricción de la jerarquía de una clase te permitirá acotar las verificaciones de subtipos cuando sabes de antemano que tu modelo tiene limitaciones en su extensión.
Únete Al Discord De Develou
Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!