Clases selladas en Kotlin

video[Tutorial Clases selladas en Kotlin

Las clases selladas (sealed) se utilizan para representar una jerarquía de cla-ses que heredan de una clase padre, de forma que tanto la clase padre como las anidadas se definen de forma conjunta en un mismo fichero. Imaginemos que queremos definir la clase padre Figura de la que extienden las subclases Triangulo, Cuadrado y Circulo. Como ves es una técnica para hacer polimorfismo.

sealed class Figura {
   class Triangulo(val lado1:Int, val lado2:Int, val lado3:Int) : Figu-ra()
   class Cuadrado(val lado:Int) : Figura()
   class Circulo(val radio:Int) : Figura()
} 
A partir de Kotlin 1.1 ya no es necesario que las clases hijo estén anidadas dentro de la clase padre:
sealed class Figura 
class Triangulo(val lado1:Int, val lado2:Int, val lado3:Int) : Figura()
class Cuadrado(val lado:Int) : Figura()
class Circulo(val radio:Int) : Figura() 
Una clase sellada se declara como abstracta, cuando creemos instancias de esta clase estamos obligados a usar alguno de sus hijos. Además, una cla-se sellada ha de definirse en un solo fichero, por lo que el número de clases hijas no podrá ser ampliada en un futuro. Lo que significa que no podremos ampliar el tipo de figuras. Por esta razón se conocen como selladas. Gracias a este hecho, cuando las utilizamos con una expresión when, como es posible verificar que se cubren todos los casos, no será necesario añadir la cláusula else.
val figura: Figura = Triangulo()
val texto = when (figura) {
   is Triangulo -> "triangu-lo"
   is Cuadrado -> "cuadrado"
   is Circulo -> "circulo"
   else -> "sin definir" 
} 
val figura:Figura = Figu-ra.Triangulo()
val texto = when (figura) {
   is Figura.Triangulo -> "trian-gulo"
   is Figura.Cuadrado -> "cuadra-do"
   is Figura.Circulo -> "circulo"
   else -> "sin definir" 
} 
Nota: Se ha puesto dos ejemplos según usemos clases anidadas o no.
En el caso de que estemos interesados en contemplar la posibilidad de que una figura no esté definida, o incluso, un tipo de figura que no requiera alma-cenar un estado, añadimos a nuestra clase sellada elementos object:
sealed class Figura 
class Triangulo(val lado1:Int, val lado2:Int, val lado3:Int) : Figura()
…
object Punto : Figura()
object SinDefinir : Figura() 
tendremos que incorporar estas opciones al bloque when:
val texto = when (figura) {
   is Triangulo -> "triangulo"
   …
   Punto -> "punto"
   SinDefinir -> "sin definir"
} 
Observa  que las nuevas opciones ya no son clases, por lo que no usaremos is en la comparación. Para entender la diferencia entre class y object vamos a ver cómo trabaja internamente. Cuando se trata de una Figura de tipo Triangulo, Cuatrado o Circulo trabajará de forma normal. Es decir, se creará un nuevo objeto en memoria con todas sus propiedades. Pero cuando sea Punto o SinDefinir, trabajará de forma diferente. La primera vez que se necesite se creará una instancia del objeto. Estos objetos solo son creados una vez en memoria. La segunda vez que se necesite una variable con este valor, no se creará un nuevo objeto, sino que esta variable será una referencia al objeto creado anteriormente[1]. Este comportamiento es igual a los enumerados, que veremos en el siguiente apartado. Un object no puede extender la clase con nuevas propiedades, pero sí que puede sobrescribir sus funciones. Veámoslo con un ejemplo:

Práctica: Clases selladas, diferencia entre class y object.

1. En Android Studio accede a Tools / Kotlin / Kotlin REPL.
2. Ejecuta el código siguiente:
sealed class Figura(open var color:Int=0) {
   abstract fun area(): Int

   class Cuadrado(override var color: Int,  
          val lado: Int) : Figura(color) {
      override fun area() = lado * lado
   }
   object Punto : Figura(1) {
      override fun area() = 0
      init {print("Punto Creado, ")}
   }
} 
val p1 = Figura.Punto
val p2 = Figura.Punto
val c = Figura.Cuadrado(2,8)

p1.color = 3
print("color c=${c.color}, ")
print("color p2=${p2.color}") 
3. Observar que la salida es: Punto Creado, color c=2, color p2=3.
4. ¿Cómo explicas que solo aparezca una vez “Punto Creado”?
5. ¿Por qué el color de p2 es 3, si lo que hemos cambiado es el color de p1?
6. ¿Por qué el color de c continúa siendo 2?
7. ¿Cómo podemos conseguir que cada punto tenga su propio color?

 Solución:

Al crearse la clase Figura, se crea una instancia por cada elemento object. En el ejemplo se creará el objeto Punto. La creación de las variables p1 y p2 no supondrán la creación de nuevos objetos, ambas serán referencias al objeto anterior. La inicialización de c, sí que supondrá la creación de un nuevo objeto en memoria, con sus dos propiedades. En la siguiente instrucción se modifica el color de p1, pero como solo hay un color también se modifica el color de p2.

1. Solo se crea un objeto Punto.
2. Las variables p1 y p2 apuntan al mismo objeto.
3. La variable c es un objeto independiente.
4. Utilizaríamos class Punto en lugar de object Punto.

En el ejemplo anterior se definían tipos de figuras. Sin embargo, no es ne-cesario que las diferentes subclases estén relacionadas entre sí. Podemos utilizar una clase sellada para representar una variable que puede tomar valo-res de diferentes tipos, aunque estos tipos no tengan ninguna relación entre sí. Por ejemplo, una solicitud HTTP puede darnos como respuesta una página web o un código de error:
sealed class RespuestaHTTP 
data class Correcta(val contenido: String) : Respuesta()
data class Error(val codigo: Int, val mesaje: String) : Respuesta()

fun getUrl(url: String): Respuesta {
   val valido = …
   if (valido) return Correcta("Contenido…")
   else        return Error(404, "No encontrada")
}

val respuesta = getUrl("/")
when (respuesta) {
   is Correcta -> println(respuesta.contenido)
   is Error -> println(respuesta.mesaje)
} 
[1] Esta forma de trabajar corresponde con el patrón Singleton