En el artículo anterior definimos la metodología a seguir y la estructura de paquetes para organizar el código. También quedó establecida la función panel. Al final del artículo anterior el contenido del método main lucía de la siguiente manera:

main.kt — Paso 2

En este artículo continuaremos con los cambios en el bloque de código de la función panel de tal manera que al finalizar luzca así:

main.kt — Paso 3

Construcción de la figura geométrica ‘Cuadrado’

Nuestro próximo objetivo es reemplazar la instanciación de la figura geométrica de tipo Square 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…

Podemos observar que la instrucción square { ... } es una función de orden superior que retorna un objeto de tipo Square. Además ya no vemos la instrucción addShape con lo cual podemos asumir que la función no solamente crea el objeto de tipo Square, sino que también lo incluye dentro del Panel y luego lo retorna.

Para incluir el objeto dentro del Panel se necesita una referencia hacia dicho Panel, sin embargo, no observamos que se esté pasando como parámetro. Es posible invocar a la función addShape debido a que ésta es una función miembro de la clase Panel. Esto nos da una pista de lo que debemos hacer para que la función square pueda incluir el objeto de tipo Square dentro del Panel sin que sea necesario pasárselo como parámetro. Si hacemos que la función square sea una función de extensión de la clase Panel bastaría con invocarla por medio de un objeto de tipo Panel. El signature de la función square sería el siguiente por el momento:

fun Panel.square(?): Square

Observa que dentro de los paréntesis como parámetro de entrada pongo un signo de interrogación ? debido a que en este momento lo único que tenemos claro es que recibe un lambda. Vamos a proceder ahora a inferir dicho lambda:

{
lines = 8
char = 'd'
}

Dentro del bloque de código del lambda podemos notar claramente que las dos instrucciones lines = 8 y char = 'd' corresponden a los parámetros que recibe un objeto de tipo Square durante su construcción. Dado que las propiedades lines y char del objeto de tipo Square son inmutables y solo se pueden establecer al instanciarlo, podemos inferir que hay un objeto intermediario que construye el objeto de tipo Square y que nos permite construir el objeto paso a paso. Este objeto intermediario se ajusta al patrón de diseño ‘Constructor’.

Antes de continuar con el análisis del lambda debemos crear un constructor de objetos de tipo Square. Para ello desplázate hasta el paquete console_shapes_dsl.builders y crea una nueva clase Kotlin llamada SquareBuilder. La implementación quedaría de la siguiente manera:

Siguiendo el patrón de diseño ‘Constructor’ es necesario que se declaren las mismas propiedades que requiere el objeto de tipo Square y que se les establezca un valor por defecto. En este caso el número de líneas por defecto será 0 y el caracter por defecto será un asterisco. La función build instanciará el objeto de tipo Square y lo retornará.

💬 Los nombres de las propiedades podrían ser diferentes si se desea, sin embargo, es más claro si se mantienen en correspondencia con las propiedades de la clase que se pretende instanciar.

Volviendo al análisis del lambda y habiendo creado una clase que nos servirá de intermediaria para la creación de objetos de tipo Square podemos inferir que su bloque de código se ejecuta dentro del contexto de un objeto de tipo SquareBuilder. Si observas bien, la instrucción lines = 8 se podría expresar como this.lines = 8 y la referencia this correspondería justamente a un objeto de tipo SquareBuilder.

💬 De haber declarado las propiedades con otro nombre, tendríamos que modificar las instrucciones lines = 8 y char = '*' por los nombres que les hayamos puesto a dichas propiedades.

Ahora que tenemos claro que el contexto dentro del bloque de código del lambda es un objeto de tipo SquareBuilder, inferimos que es un lambda con receptor y que dicho receptor es de tipo SquareBuilder. También vemos claramente que el lambda no recibe parámetros de entrada dado que no muestra la declaración de variables con la notación de flecha y tampoco usa la referencia it en su bloque de código. La última línea del bloque de código del lambda no retorna un valor así que inferimos que el lambda no retorna un valor, o lo que es lo mismo, retorna un valor de tipo Unit . Luego de hacer este análisis concluimos que el tipo de función del lambda corresponde a SquareBuilder.() -> Unit.

Ahora que tenemos definido el tipo de función del lambda, retomamos el análisis de la función square desde donde habíamos quedado, es decir, la instrucción square es una función de extensión de la clase Panel y a su vez es también una función de orden superior que recibe el lambda y que retorna un objeto de tipo Square, quedando el signature de la siguiente manera:

fun Panel.square(init: SquareBuilder.() -> Unit): Square

💬 Llamo init al lambda dado que en esencia su bloque de código contiene el establecimiento de las variables que se utilizarán para la construcción del objeto de tipo Square.

Ahora que tenemos definido el signature de la función square , desplázate hasta el paquete console_shapes_dsl.external y añade el siguiente código en el archivo Extension.kt:

fun Panel.square(init: SquareBuilder.() -> Unit): Square {

}

Sabemos que la función debe cumplir con los siguientes puntos:
— Crear un objeto de tipo Square con los parámetros correspondientes al bloque de código del lambda.
— Incluir el objeto de tipo Square dentro del Panel.
— Retornar el objeto de tipo Square.

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

fun Panel.square(init: SquareBuilder.() -> Unit): Square {
val builder = SquareBuilder()
builder.init()
val square = builder.build()
this.addShape(square)
return square
}

Finalmente podríamos reducir el código de la función square a una sola línea valiéndonos de las funciones de ámbito apply y also de la siguiente manera:

Si te desplazas al método main te darás cuenta de que ya no te marca errores. Por el momento así debería lucir el método main:

main.kt — Paso 3

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

Por ahora lo que hemos hecho es suficiente. Las figuras ‘Triángulo’ y ‘Rombo’ se harán prácticamente igual por lo que será importante abstraer el comportamiento en una clase superior de la cual se pueda heredar y así evitar la redundancia, sin embargo, lo haremos al finalizar las 3 figuras para que queden más claras en su estructura y de paso consolidar el conocimiento por repetición. Puedes incluso intentarlo por tu cuenta y comparar el resultado con lo que haremos en el siguiente artículo.

--

--

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.