Kotlin DSL | Construcción del DSL: 4 — Espacios en blanco y el objeto ‘Figura Compuesta’

Glenn Sandoval
Kotlin en Android
Published in
7 min readJun 10, 2021

Inserción de espacios en blanco entre figuras y combinación de figuras para crear figuras compuestas

En el artículo anterior quedaron definidas las clases constructoras de objetos de tipo Triangle y Rhombus y la funciones de extensión triangle y rhombus que utilizan dichas clases constructoras para crear y añadir ‘Triángulos’ y ‘Rombos’ dentro del Panel. También se definió la clase abstracta ShapeBuilder que reúne las características comunes entre las clases constructoras. Al final del artículo anterior el contenido del método main lucía de la siguiente manera:

main.kt — Paso 5

En este artículo cambiaremos la manera en que añadimos espacios en blanco y también la manera en que creamos y añadimos figuras compuestas dentro del Panel.

La función ‘space’ para añadir espacios en blanco

El próximo objetivo es cambiar todas las instrucciones addShape(Space) por instrucciones space() quedando el código del método main así:

main.kt — Paso 6

Claramente la instrucción space() es una invocación a una función. Podemos inferir entonces que dentro de dicha función se ejecuta la instrucción addShape(Space) para añadir el objeto Space dentro del Panel.

💬 Recuerda que la instrucción addShape(Space) lleva implícita la referencia this siendo también posible invocarla así: this.addShape(Space).

La función space no es tan diferente a las funciones square, triangle y rhombus siendo que el objeto Space también es una figura pues extiende a la clase Shape. Podemos afirmar entonces que para invocar a la función addShape dentro de la función space necesitamos una referencia hacia el objeto de tipo Panel tal y como también la necesitamos dentro de las otras tres funciones. Podemos concluir que la función space es una función de extensión de la clase Panel, al igual que las funciones square, triangle y rhombus.

El signature de la función space quedaría así:

fun Panel.space()

Desplázate hasta el paquete console_shapes_dsl.external y añade el siguiente código en el archivo Extension.kt:

fun Panel.space() {

}

El único objetivo de la función es añadir la figura de tipo Space dentro del Panel. La implementación de la función es la siguiente:

💬 Si ejecutas el código verás que nuevamente funciona.

Construcción de la figura geométrica compuesta

El próximo objetivo es reemplazar las instrucciones addShape(ComposedShape(shapeX, shapeY, ComposedShape.Operation.XYZ)) por instrucciones composed { shapeX xyz shapeY }, donde shapeX y shapeY pueden ser instancias de las clases Square, Triangle, Rhombus o ComposedShape y Operation.XYZ o xyz corresponden a la operación a realizar, ya sea Operation.UNION u Operation.INTERSECTION.

Podemos separar dichas instrucciones en 2 partes:

  • Cambiar la instrucción ComposedShape(shapeX, shapeY, ComposedShape.Operation.XYZ) por la instrucción shapeX xyz shapeY
  • Cambiar la invocación a la función addShape por la invocación a la función composed

Además ambas partes deben ser intercambiables, es decir, al tener expresiones como composed { ComposedShape(shapeX, shapeY, ComposedShape.Operation.XYZ) } o addShape(shapeX xyz shapeY) el resultado debería ser el mismo.

Dadas estas equivalencias podemos decir que la instrucción shapeX xyz shapeY crea un objeto de tipo ComposedShape y que la invocación a la función composed agrega una figura de tipo ComposedShape dentro del Panel.

🔗 Si se te hace un poco extraña la instrucción shapeX xyz shapeY puedes echarle un vistazo a al apartado Notación infix que expuse en un artículo anterior.

La funciones union e intersection en notación infix

Vamos a iniciar substituyendo la instrucción ComposedShape(shapeX, shapeY, ComposedShape.Operation.XYZ) por la instrucción shapeX xyz shapeY. El código del método main al que queremos llegar es el siguiente:

main.kt — Paso 7

Las nuevas funciones y sus respectivas tareas son las siguientes:

  • La función union debe realizar la unión de dos figuras de tipo Shape y retornar una nueva figura de tipo ComposedShape. La operación a realizar corresponde al parámetro operation cuyo valor es Operation.UNION.
  • La función intersection debe realizar la intersección de dos figuras de tipo Shape y retornar una nueva figura de tipo ComposedShape. La operación a realizar corresponde al parámetro operation cuyo valor es Operation.INTERSECTION.

Los signatures de las funciones union e intersection son los siguientes:

infix fun Shape.union(shape: Shape): ComposedShape
infix fun Shape.intersection(shape: Shape): ComposedShape

Según el código al que queremos llegar, las funciones union e intersection se deben poder invocar con notación infix y dado que una función infix tiene como restricción recibir solamente 1 parámetro de entrada, será necesario que sean funciones de extensión de la clase Shape de manera que podremos usar la referencia this y el parámetro de entrada de tipo Shape para hacer la combinación de ambas figuras.

Desplázate hasta el paquete console_shapes_dsl.external y añade el siguiente código en el archivo Extension.kt:

infix fun Shape.union(shape: Shape): ComposedShape {

}

infix fun Shape.intersection(shape: Shape): ComposedShape {

}

La función union deberá crear una figura de tipo ComposedShape realizando la combinación de ambas figuras con la operación Operation.UNION y luego retornar dicha figura. La función de extensión union quedaría de la siguiente manera:

De manera similar, la función intersection deberá realizar la combinación con la operación Operation.INTERSECTION. La función de extensión intersection quedaría de la siguiente manera:

💬 Si ejecutas el código verás que nuevamente funciona.

— La función composed para añadir un objeto ComposedShape al Panel

Ahora que ya contamos con la creación de figuras de tipo ComposedShape utilizando la notación infix, debemos cambiar las invocaciones a la función addShape por invocaciones a la función composed que lo único que hará será añadir un objeto de tipo ComposedShape al Panel. El código del método main al que queremos llegar es el siguiente:

main.kt — Paso 8

Vamos a analizar la función composed por partes:
—Los bloques de código entre llaves implican que la función composed recibe como único parámetro de entrada un lambda. Es decir, la función composed es una función de orden superior.
—Todos los bloques de código correspondientes al lambda finalizan con invocaciones a las funciones union e intersection, por lo tanto, el lambda debe retornar un objeto de tipo ComposedShape.
— Ninguno de los bloques de código correspondientes al lambda presenta la notación con la flecha, y siendo que no se observa el uso de ninguna referencia ni tampoco de la referencia implícita it, se asume que el lambda no recibe parámetros de entrada.
— Dentro de los lambda hay invocaciones a las funciones union e intersection que son funciones de extensión de la clase Panel , por lo tanto, es claro que el bloque de código del lambda se ejecuta dentro del ámbito de un objeto de tipo Panel, tratándose entonces de un lambda con receptor de tipo Panel.

Algo que no podemos inferir directamente del código al que queremos llegar es el tipo de dato de retorno de la función composed. Aparentemente no debería retornar nada dado que no se hace una asignación a alguna variable en ninguna de sus invocaciones. Sin embargo, dado que la función composed está al mismo nivel que las funciones square , triangle y rhombus y en esencia realiza la misma tarea que dichas funciones — crear un figura e ingresarla dentro del Panel — podríamos dotarla de un valor de retorno que corresponda al objeto de tipo ComposedShape que añade al Panel. De esta manera si se deseara mantener una referencia hacia dicho objeto, eventualmente se podría usar para ser combinado con otras figuras.

Luego de este análisis, el signature de la función composed quedaría de la siguiente manera:

fun Panel.composed(init: Panel.() -> ComposedShape): ComposedShape

💬 Llamo init al lambda para mantener el estilo utilizado en las otras funciones homólogas a ésta.

Desplázate hasta el paquete console_shapes_dsl.external y añade el siguiente código en el archivo Extension.kt:

fun Panel.composed(init: Panel.() -> ComposedShape): ComposedShape {

}

Antes de escribir el cuerpo de la función composed puntualicemos lo que debe hacer:
— Ejecutar el bloque de código del lambda y guardar en una variable el objeto de tipo ComposedShape que retorna.
— Agregar el objeto de tipo ComposedShape dentro del Panel.
— Retornar el objeto de tipo ComposedShape.

La definición de la función composed quedaría de la siguiente manera:

fun Panel.composed(init: Panel.() -> ComposedShape): ComposedShape {
val shape = init()
this.addShape(shape)
return shape
}

La función anterior la podemos reducir a una sola línea valiéndonos de la función de ámbito also de la siguiente manera:

💬 Si ejecutas el código verás que nuevamente funciona.

Puedes desplazarte hasta el método main y probar que tanto la función composed como la invocación a las funciones infix son intercambiables con la invocación a la función addShape y la creación de figuras compuestas de manera directa.

Por ejemplo podrías añadir figuras compuestas al Panel sin ningún problema de la siguiente forma:

Estamos muy cerca del objetivo final. Solo nos falta poder combinar figuras con los operadores + y - , siendo el operador + correspondiente a la unión y el operador - correspondiente a la intersección. En el próximo artículo haremos eso además de una pequeña optimización.

--

--

Glenn Sandoval
Kotlin en Android

I’m a software developer who loves learning and making new things all the time. I especially like mobile technology.