Las clases selladas en Kotlin son otra de las novedades que tenemos sobre Java, y que abren otro nuevo mundo de posibilidades
Una clase sellada (o sealed class) permite representar jerarquías restringidas en las que un objeto sólo puede ser de un tipo de las dadas.
Es decir, tenemos un clase con un número específico de subclases. Lo que obtenemos al final es un concepto muy similar al de un enumerado. La diferencia radica en que en los enumerados sólo tenemos un único objeto por tipo, mientras que en las sealed classes podemos tener varios objetos de la misma clase.
Esta diferencia hace que podamos almacenar estado. Esto nos va a traer algunas ventajas que veremos en un momento, y también abre las puertas hacia ciertas ideas funcionales.
Si todo esto te apasiona tanto como a mí, te animo a que te apuntes a mi training gratuito donde te contaré todo lo que necesitas para aprende a crear tus Apps Android en Kotlin desde cero.
Cómo utilizar la sealed classes
Implementar una sealed class en realidad es muy sencillo. Vamos a poner como ejemplo un conjunto de operaciones que se pueden ejecutar sobre un entero.
La implementación sería la siguiente:
sealed class Operation { class Add(val value: Int) : Operation() class Substract(val value: Int) : Operation() class Multiply(val value: Int) : Operation() class Divide(val value: Int) : Operation() }
Creamos una sealed class llamada Operation
, que contiene cuatro tipo de operaciones: suma, resta, multiplicación y división.
Lo bueno de esto es que ahora las operaciones when
nos requerirán dar opciones para todos los posibles tipos:
fun execute(x: Int, op: Operation) = when (op) { is Operation.Add -> x + op.value is Operation.Substract -> x - op.value is Operation.Multiply -> x * op.value is Operation.Divide -> x / op.value }
Si te dejas alguna de las opciones fuera, el when
se quejará y no compilará. Si las implementas todas, no necesitas sentencia else
. Y en general no será muy recomendado usarlo porque así estamos seguros de que estamos haciendo lo correcto para todas ellas.
Esto también es genial en el caso de que decidas añadir una nueva operación, porque fallará en tiempo de compilación y no de ejecución. Añade un par de operaciones más: incrementar y decrementar:
sealed class Operation { ... class Increment() : Operation() class Decrement() : Operation() }
Verás que ahora el compilador te avisa de que hay un problema. Sólo tienes que añadir los casos de estas nuevas operaciones:
fun execute(x: Int, op: Operation) = when (op) { ... is Operation.Increment -> x + 1 is Operation.Decrement -> x - 1 }
Moviendo los efectos de lado a un único punto
Los efectos de lado (o side effects) son un concepto muy típico de la programación funcional. La programación funcional se basa mucho en la idea de que para una función y unos parámetros dados, el resultado debe ser siempre el mismo.
Cualquier estado que se modifique puede hacer que esto no sea verdad. Pero cualquier programa necesita modificar estados, comunicarse con elementos de entrada/salida, etc. Así que es importante minimizar las operaciones en ciertos puntos muy concretos que se puedan aislar con facilidad.
Por ejemplo, el realizar cualquier operación sobre una vista de Android se puede considerar un efecto de lado, pues se está modificando el estado de las vistas y las funciones no son conscientes de ello.
Podríamos crearnos una sealed class que nos permitiera hacer operaciones sobre nuestras vistas. Siguiendo un poco la idea del ejemplo anterior:
sealed class UiOp { object Show: UiOp() object Hide: UiOp() class TranslateX(val px: Float): UiOp() class TranslateY(val px: Float): UiOp() } fun execute(view: View, op: UiOp) = when (op) { UiOp.Show -> view.visibility = View.VISIBLE UiOp.Hide -> view.visibility = View.GONE is UiOp.TranslateX -> view.translationX = op.px is UiOp.TranslateY -> view.translationY = op.px }
Fíjate que las operaciones que no tienen estado pueden ser objetos, pues no necesitamos distintas instancias.
Ahora puedes crearte un objeto Ui
que acumule todas las operaciones de interfaz que queremos hacer sobre una vista, pero que no las ejecute hasta el momento que queramos.
Tendremos una descripción de lo que queremos hacer, y luego podremos crear un componente que las ejecute:
class Ui(val uiOps: List = emptyList()) { operator fun plus(uiOp: UiOp) = Ui(uiOps + uiOp) }
La clase Ui
almacena un listado de operaciones, y especifica un operador de suma que nos ayudará a que quede todo un poco más limpio y sencillo de leer. Ahora podemos especificar el listado de operaciones que queremos realizar:
val ui = Ui() + UiOp.Show + UiOp.TranslateX(20f) + UiOp.TranslateY(40f) + UiOp.Hide run(view, ui)
Y posteriormente ejecutarlo. Aquí solo estoy utilizando una función run
, pero esto podría ser a su vez un objeto.
fun run(view: View, ui: Ui) { ui.uiOps.forEach { execute(view, it) } }
Imagina la potencia que te da esto. Ahora mismo lo único que se hace es ejecutar las operaciones secuencialmente.
Pero podrías crearte un objeto que ejecutase animaciones para cada una de las operaciones, y sólo cambiando eso, el resto del programa funcionaría de la misma forma.
Conclusión
El concepto de las sealed classes es muy sencillo, pero es la base de un montón de ideas nuevas a las que hay que acostumbrarse si uno no conoce mucho sobre programación funcional.
Yo mismo reconozco que aún no soy capaz de sacarle toda la potencia a esto por mis limitaciones en el mundo funcional.
¿Te gustaría comenzar hoy a dar el siguiente paso? Te recomiendo que entres a mi training gratuito aquí.