Clases selladas en detalle con varios ejemplos.¶
Sealed Class¶
Las clases selladas (sealed class
) en Kotlin son una característica especial que se utiliza para representar jerarquías de
clases restringidas, en las cuales una clase tiene un número limitado de subtipos.
Se utilizan principalmente cuando se desea que un valor pueda ser de uno entre varios tipos, pero no de cualquier otro tipo.
Son especialmente útiles en el manejo de estados o en la implementación del patrón de diseño de Visitor.
Algunas características clave de las clases selladas:
- Extensibilidad limitada: Solo las clases que están en el mismo archivo que la clase sellada pueden extenderla. Esto asegura que todos los subtipos sean conocidos en tiempo de compilación y ayuda a evitar errores en tiempo de ejecución.
- Uso con
when
: Las clases selladas son muy útiles con la expresiónwhen
en Kotlin, ya que puedes asegurarte de que has manejado todos los casos posibles sin necesidad de un bloqueelse
.
Ejemplos de uso de clases selladas:¶
Ejemplo 1:¶
Supongamos que estás construyendo una aplicación y necesitas representar diferentes tipos de operaciones en una calculadora. Podrías tener operaciones como Suma, Resta, Multiplicación y División. Aquí hay un ejemplo de cómo podrías hacerlo usando clases selladas:
sealed class CalculadoraOperacion {
data class Suma(val valor1: Double, val valor2: Double) : CalculadoraOperacion()
data class Resta(val valor1: Double, val valor2: Double) : CalculadoraOperacion()
data class Multiplicacion(val valor1: Double, val valor2: Double) : CalculadoraOperacion()
data class Division(val valor1: Double, val valor2: Double) : CalculadoraOperacion()
}
fun ejecutarOperacion(operacion: CalculadoraOperacion): Double {
return when (operacion) {
is CalculadoraOperacion.Suma -> operacion.valor1 + operacion.valor2
is CalculadoraOperacion.Resta -> operacion.valor1 - operacion.valor2
is CalculadoraOperacion.Multiplicacion -> operacion.valor1 * operacion.valor2
is CalculadoraOperacion.Division -> operacion.valor1 / operacion.valor2
}
}
fun main() {
val suma = CalculadoraOperacion.Suma(10.0, 20.0)
println("El resultado de la suma es: ${ejecutarOperacion(suma)}")
}
- En este ejemplo,
CalculadoraOperacion
es una clase sellada que tiene cuatro subtipos:Suma
,Resta
,Multiplicacion
, yDivision
. - Cada uno de estos subtipos tiene su propia implementación y datos asociados.
- La función
ejecutarOperacion
acepta unCalculadoraOperacion
y usawhen
para determinar qué operación realizar. - Una de las ventajas aquí es que si añades un nuevo subtipo de
CalculadoraOperacion
y te olvidas de manejarlo enejecutarOperacion
, el compilador te advertirá que elwhen
no está manejando todos los casos posibles, ayudándote a evitar errores.
Ejemplo 2:¶
Supongamos que estás desarrollando un sistema de notificaciones para una aplicación y tienes varios tipos de notificaciones, como mensajes, alertas y advertencias.
Puedes usar una clase sellada para modelar estos diferentes tipos de notificaciones.
// Definición de la clase sellada
sealed class Notificacion {
class Mensaje(val contenido: String) : Notificacion()
class Alerta(val titulo: String, val descripcion: String) : Notificacion()
class Advertencia(val mensaje: String) : Notificacion()
}
// Función que maneja las notificaciones
fun manejarNotificacion(notificacion: Notificacion) {
when (notificacion) {
is Notificacion.Mensaje -> println("Tienes un nuevo mensaje: ${notificacion.contenido}")
is Notificacion.Alerta -> println("Alerta: ${notificacion.titulo} - ${notificacion.descripcion}")
is Notificacion.Advertencia -> println("Advertencia: ${notificacion.mensaje}")
}
}
// Función principal para demostrar el uso de las clases selladas
fun main() {
val mensaje = Notificacion.Mensaje("Bienvenido a Kotlin!")
val alerta = Notificacion.Alerta("Error de Servidor", "El servidor no responde.")
val advertencia = Notificacion.Advertencia("Batería baja.")
manejarNotificacion(mensaje)
manejarNotificacion(alerta)
manejarNotificacion(advertencia)
}
En este ejemplo:
Notificacion
es una clase sellada con tres subclases:Mensaje
,Alerta
, yAdvertencia
.- Cada tipo de notificación puede contener diferentes tipos de información. Por ejemplo,
Mensaje
solo tiene contenido,Alerta
tiene título y descripción, yAdvertencia
solo tiene un mensaje. - La función
manejarNotificacion
usa unwhen
para determinar el tipo de notificación y actuar en consecuencia. - En la función
main
, se crean instancias de diferentes tipos de notificaciones y se pasan a la funciónmanejarNotificacion
.
La belleza de este enfoque es que si decides añadir un nuevo tipo de notificación en el futuro, el compilador te advertirá
en los lugares donde no estés manejando este nuevo tipo, gracias a las garantías de exhaustividad proporcionadas por las
clases selladas en combinación con when
.
Ejemplo 3:¶
Las clases que están dentro de una clase sellada pueden tener sus propios métodos y propiedades adicionales, no solo los que se definen en su constructor primario.
Esto te permite tener una estructura de clases muy flexible y rica en comportamiento. Cada subclase puede tener su propia implementación de métodos y sus propias propiedades.
sealed class Dispositivo {
abstract fun mostrarInfo(): String
class Smartphone(val marca: String, val modelo: String, val sistemaOperativo: String) : Dispositivo() {
private val appsInstaladas = mutableListOf<String>()
fun instalarApp(nombreApp: String) {
appsInstaladas.add(nombreApp)
}
override fun mostrarInfo(): String {
return "Smartphone $marca $modelo con SO $sistemaOperativo. Apps instaladas: $appsInstaladas"
}
}
class Tableta(val marca: String, val tamañoPantalla: Double) : Dispositivo() {
var nivelBateria = 100
fun usarBateria(porcentaje: Int) {
nivelBateria -= porcentaje
}
override fun mostrarInfo(): String {
return "Tableta $marca con pantalla de $tamañoPantalla pulgadas. Batería al $nivelBateria%"
}
}
}
fun describirDispositivo(dispositivo: Dispositivo) {
println(dispositivo.mostrarInfo())
}
fun main() {
val miSmartphone = Dispositivo.Smartphone("Pixel", "5", "Android")
miSmartphone.instalarApp("Twitter")
miSmartphone.instalarApp("Spotify")
val miTableta = Dispositivo.Tableta("iPad", 10.2)
miTableta.usarBateria(10)
describirDispositivo(miSmartphone)
describirDispositivo(miTableta)
}
En este ejemplo:
Dispositivo
es una clase sellada con dos subclases:Smartphone
yTableta
.Smartphone
tiene una propiedadappsInstaladas
que no se define en el constructor, sino en el cuerpo de la clase. También tiene un métodoinstalarApp
para agregar aplicaciones a la lista.Tableta
tiene una propiedad mutablenivelBateria
y un métodousarBateria
para simular el uso de la batería.- Ambas clases
Smartphone
yTableta
implementan el método abstractomostrarInfo
de la clase selladaDispositivo
, pero cada una con su propia lógica.
Este ejemplo muestra cómo puedes tener estructuras de clases complejas y bien organizadas utilizando clases selladas, aprovechando la posibilidad de añadir propiedades y métodos específicos en cada subclase.
Ejemplo 4:¶
Las clases que extienden una clase sellada en Kotlin no necesitan estar anidadas dentro de la clase sellada; sin embargo, deben estar en el mismo archivo que la clase sellada.
Esto se debe a que el objetivo de una clase sellada es restringir la jerarquía de herencia a un conjunto conocido de subtipos, lo que se facilita al requerir que todas las subclases estén en el mismo archivo.
Si las clases Smartphone
y Tableta
están fuera de la clase sellada Dispositivo
, pero en el mismo archivo,
seguirían siendo subclases válidas de Dispositivo
. Funcionalmente, sería lo mismo en términos de cómo puedes usar estas clases
y cómo funcionaría la comprobación de tipos en tiempo de compilación.
sealed class Dispositivo {
abstract fun mostrarInfo(): String
}
class Smartphone(val marca: String, val modelo: String, val sistemaOperativo: String) : Dispositivo() {
private val appsInstaladas = mutableListOf<String>()
fun instalarApp(nombreApp: String) {
appsInstaladas.add(nombreApp)
}
override fun mostrarInfo(): String {
return "Smartphone $marca $modelo con SO $sistemaOperativo. Apps instaladas: $appsInstaladas"
}
}
class Tableta(val marca: String, val tamañoPantalla: Double) : Dispositivo() {
var nivelBateria = 100
fun usarBateria(porcentaje: Int) {
nivelBateria -= porcentaje
}
override fun mostrarInfo(): String {
return "Tableta $marca con pantalla de $tamañoPantalla pulgadas. Batería al $nivelBateria%"
}
}
fun describirDispositivo(dispositivo: Dispositivo) {
println(dispositivo.mostrarInfo())
}
fun main() {
val miSmartphone = Smartphone("Pixel", "5", "Android")
miSmartphone.instalarApp("Twitter")
miSmartphone.instalarApp("Spotify")
val miTableta = Tableta("iPad", 10.2)
miTableta.usarBateria(10)
describirDispositivo(miSmartphone)
describirDispositivo(miTableta)
}
En este código, Smartphone
y Tableta
son clases independientes que extienden la clase sellada Dispositivo
y proporcionan
su propia implementación de mostrarInfo()
.
Esto mantiene la capacidad de Dispositivo
para limitar sus subtipos a un conjunto conocido y manejable, aprovechando las
ventajas de las clases selladas en Kotlin.