Kotlin DSL | Construcción del DSL: 3 — Los objetos ‘Triángulo’ y ‘Rombo’
Construcción de los objetos ‘Triángulo’ y ‘Rombo’
Puedes ir a cualquier artículo de esta serie haciendo clic en alguno de los siguientes enlaces:
Kotlin DSL
- Introducción
- Conocimiento base para construir DSLs con Kotlin — Parte 1
- Conocimiento base para construir DSLs con Kotlin — Parte 2
- Código Base: Proyecto Shapes-DSL
- Construcción del DSL: 1 — Organización de paquetes y el objeto ‘Panel’
- Construcción del DSL: 2 — El objeto ‘Cuadrado’
- Construcción del DSL: 3 — Los objetos ‘Triángulo’ y ‘Rombo’
- Construcción del DSL: 4 — Espacios en blanco y el objeto ‘Figura Compuesta’
- Construcción del DSL: 5 — Los operadores ‘plus’ y ‘minus’ y funciones ‘inline’
- Construcción del DSL: 6 — La anotación @DslMarker
- Experimentación y conclusiones
En el artículo anterior quedó definido el constructor de objetos de tipo Square
y la función de extensión square
que utiliza dicho constructor para crear y añadir ‘Cuadrados’ dentro del Panel. Al final del artículo anterior el código del método main
lucía de la siguiente manera:
Construcción de la figura geométrica ‘Triángulo’
Repitiendo todo el procedimiento que hicimos con la figura ‘Cuadrado’ vamos a reemplazar la instanciación de la figura geométrica de tipo Triangle
dentro del bloque de código de la función panel
, así como su posterior inserción dentro del Panel. Es decir, reemplazaremos las instrucciones…
…por…
Claramente la función square
y la función triangle
son idénticas. Podemos asumir entonces que la función triangle
es una función de extensión de la clase Panel
y a su vez es de orden superior que recibe un lambda y que retorna un objeto de tipo Triangle
. El signature sería el siguiente:
fun Panel.triangle(?): Triangle
💬 Pongo temporalmente un signo de interrogación como parámetro de entrada ya que lo único que sabemos es que recibe un lambda.
Dado que la función triangle
sigue el mismo patrón que la función square
llegamos a la conclusión de que la función sigue el patrón de diseño ‘Constructor’ para construir objetos de tipo Triangle
así que procedemos a crear la clase constructora de figuras ‘Triángulo’. Desplázate hasta el paquete console_shapes_dsl.builders
y crea una nueva clase Kotlin llamada TriangleBuilder
. La implementación quedaría de la siguiente manera:
💬 Aquí empezamos a notar código duplicado. Al final del artículo abstraeremos el código a una clase Padre.
El lambda es análogo al de la función square
siendo ahora posible definir su tipo de función de la siguiente manera: TriangleBuilder.() -> Unit
.
Finalmente el signature de la función triangle
queda de la siguiente manera:
fun Panel.triangle(init: TriangleBuilder.() -> Unit): Triangle
Desplázate hasta el paquete console_shapes_dsl.external
y añade el siguiente código en el archivo Extension.kt
:
fun Panel.triangle(init: TriangleBuilder.() -> Unit): Triangle {
}
Sabemos que la función debe cumplir con los siguientes puntos:
— Crear un objeto de tipo Triangle
con los parámetros correspondientes al bloque de código del lambda.
— Incluir el objeto de tipo Triangle
dentro del Panel.
— Retornar el objeto de tipo Triangle
.
La implementación de la función quedaría de la siguiente manera:
fun Panel.triangle(init: TriangleBuilder.() -> Unit): Triangle {
val builder = TriangleBuilder()
builder.init()
val triangle = builder.build()
this.addShape(triangle)
return triangle
}
💬 Si ejecutas el código verás que nuevamente funciona.
La función anterior la podemos reducir a una sola línea valiéndonos de las funciones de ámbito apply
y also
de la siguiente manera:
Ahora puedes desplazarte al método main
y ejecutar el código sin problemas. Por el momento el método main
luce así:
Construcción de la figura geométrica ‘Rombo’
Con la experiencia adquirida, luego de substituir la creación e inserción de las figuras ‘Cuadrado’ y ‘Triángulo’ dentro del Panel, podemos avanzar más rápido esta vez.
El objetivo ahora es reemplazar las instrucciones…
…por…
Como ya sabemos que vamos a tener que crear un intermediario que se encargue de la creación de figuras de tipo Rhombus
, así que procederemos a crearlo. Desplázate hasta el paquete console_shapes_dsl.builders
y crea una nueva clase Kotlin llamada RhombusBuilder
. La implementación quedaría de la siguiente manera:
Ahora saltamos a la definición de la función rhombus
y guiados por las funciones square
y triangle
podemos decir que es una función de extensión de la clase Panel
, que a su vez es una función de orden superior que recibe un lambda cuyo tipo de función es RhombusBuilder.() -> Unit
y que retorna un objeto de tipo Rhombus
. El signature de la función sería el siguiente:
fun Panel.rhombus(init: RhombusBuilder.() -> Unit): Rhombus
Desplázate hasta el paquete console_shapes_dsl.external
y añade el siguiente código en el archivo Extension.kt
:
fun Panel.rhombus(init: RhombusBuilder.() -> Unit): Rhombus {
}
Al igual que las funciones square
y triangle
, la función rhombus
debe cumplir con los siguientes puntos:
— Crear un objeto de tipo Rhombus
con los parámetros correspondientes al bloque de código del lambda.
— Incluir el objeto de tipo Rhombus
dentro del Panel.
— Retornar el objeto de tipo Rhombus
.
La implementación de la función quedaría de la siguiente manera:
fun Panel.rhombus(init: RhombusBuilder.() -> Unit): Rhombus {
val builder = RhombusBuilder()
builder.init()
val rhombus = builder.build()
this.addShape(rhombus)
return rhombus
}
💬 Si ejecutas el código verás que nuevamente funciona.
La función anterior la podemos reducir a una sola línea valiéndonos de las funciones de ámbito apply
y also
de la siguiente manera:
Desplázate al método main
y asegúrate de que todo está en orden. El método main
debería lucir así:
Podríamos dejar los 3 constructores de figuras tal y como están, sin embargo, como buena práctica deberíamos abstraer el código común a una clase padre facilitando así la inclusión de más figuras en el futuro.
Abstracción del código común de las clases constructoras
El código común de las 3 clases constructoras es el siguiente:
— Definición de la propiedad lines
inicializada en 0
— Definición de la propiedad char
inicializada con el caracter asterisco *
— Definición de la función build
que retorna un objeto de tipo Shape
Como buena práctica también podríamos crear una constante para establecer el caracter por defecto que tendrá la figura en caso de que no se establezca durante su construcción.
Desplázate hasta el paquete console_shapes_dsl.builders
y crea una nueva clase abstracta Kotlin llamada ShapeBuilder
. La implementación quedaría de la siguiente manera:
La función build
la marcamos como abstracta para que sea definida en cada clase constructora particular.
Ahora desplázate hasta la clase SquareBuilder
y haz que extienda a la clase ShapeBuilder
. Luego elimina la declaración de las propiedades lines
y char
y finalmente modifica la función build
de la siguiente manera:
override fun build() = Square(lines, char)
La clase SquareBuilder
completa quedaría así:
💬 Asegúrate de que todo funciona perfectamente antes de continuar con la modificación de las otras 2 clases constructoras.
Puedes modificar las clases TriangleBuilder
y RhombusBuilder
de manera análoga a la clase SquareBuilder
. La definición de dichas clases sería la siguiente:
TriangleBuilder
:
RhombusBuilder
:
Hemos llegado al final de este artículo. Solo nos queda definir la funciones space
y composed
que añaden los espacios en blanco y las figuras compuestas al Panel respectivamente. En el siguiente artículo crearemos estas funciones y además aplicaremos más conceptos y características de Kotlin para crear un DSL más idiomático.
Continúa en el siguiente artículo con Construcción del DSL: 4 — Espacios en blanco y el objeto ‘Figura Compuesta’
- Puedes acceder a la documentación oficial sobre funciones de orden superior y lambdas en el siguiente enlace: https://kotlinlang.org/docs/lambdas.html
- Puedes acceder a la documentación oficial sobre lambdas con receptor en el siguiente enlace: https://kotlinlang.org/docs/lambdas.html#function-literals-with-receiver
- Puedes acceder a la documentación oficial sobre funciones de ámbito en el siguiente enlace: https://kotlinlang.org/docs/scope-functions.html