Implementa clases iterables en Xojo

Los iteradores son las estructuras que nos permiten recorrer todos los miembros de una colección, sin que exista la necesidad previa de conocer cuál es la cantidad de dichos elementos. Para ello encontramos en Xojo las sentencias For Each… Next. ¿Cuáles son las principales diferencias en comparación con el bucle For…Next convencional?

La primera es que no podemos presumir que estemos iterando los miembros de la colección en un orden determinado, tal y como sí ocurriría por ejemplo cuando accedemos utilizando el valor de índice en un Array mediante el bucle convencional For… Next. La segunda diferencia es que el iterador será inválido cuando se modifiquen los elementos o cantidad de elementos de la colección mientras que los estemos recorriendo. Por omisión, en Xojo encontramos colecciones por las que podemos iterar de esta forma prescindiendo así de la necesidad de obtener un contador de elementos previamente: los ya mencionados Array y también los Diccionarios. Ahora bien, ¿querrías añadir este comportamiento en tus propias clases, haciendo así que su manejo sea más flexible? La respuesta está en dos interfaces de clase incluidas de serie en Xojo: Iterator e Iterable.

Puedes descargar el proyecto de ejemplo utilizado en este tutorial desde este enlace.

uso de Iterator

A la hora de implementar este tipo de comportamiento en nuestras clases, de modo que puedan utilizarse en las estructuras For Each… Next, puede decirse que el trabajo real propiamente dicho corre a cargo de la Interfaz de clase Xojo.Core.Iterator. En ella encontramos la definición de métodos que ha de implementar la clase y que son los responsables de avanzar por cada uno de los elementos de la colección, así como de devolver el valor actual de la colección:

  • MoveNext. Avanza al siguiente elemento de la colección, devolviendo True cuando no se hayan iterado ya por todos los elementos de la colección, o bien devolviendo False cuando se haya sobrepasado el úlimo elemento.
  • Value. Este método de la Interfaz de Clase se encarga de devolver como tipo de dato Auto el elemento actual de la colección.

Es fácil imaginar que de lo anterior se desprende la necesidad de que la clase sea la responsable de llevar en todo momento un contador (Propiedad) que se actualice con cada una de las invocaciones sobre el método MoveNext. También utilizaremos dicha propiedad para comprobar si se ha superado la cantidad máxima de elementos. La única observación es que la propiedad ha de inicializarse con el valor de -1 (es decir, un valor inválido), dado que la documentación indica que antes de obtener el primero de los elementos iterables de la colección es necesario invocar MoveNext, situando así el cursor sobre el primero de los elementos iterables.

Por otra parte, manejaremos todos los casos en los que se produzca un error en el iterador lanzando una excepción IteratorException.

Para verlo en acción lo mejor es crear un proyecto de ejemplo donde utilizaremos una clase Persona que se encargará de almacenar en cada una de sus instancias todas las posibles direcciones asociadas a una persona en concreto.

Comencemos definiendo la clase Persona como un simple conjunto de Propiedades:

  • nombre As String
  • primerApellido As String
  • segundoApellido As String

Añadimos también la propiedad encargada de almacenar en un Array las diferentes direcciones postales asignadas a una misma Persona:

direcciones As Direccion

Con ello, habremos terminado la definición de nuestra clase Persona. Sencilla, pero nos sirve para el propósito del tema expuesto.

También mantendremos simple (e incompleta) la definición de la clase Direccion con el uso de propiedades igualmente de ámbito público, de modo que nos sirva al propósito del tema principal:

  • calle As Text
  • numero As Integer
  • piso As Integer

Creando un Iterador

Con las dos clases principales necesarias para nuestro ejemplo (Persona y Direccion) ya podemos ocuparnos de crear la clase responsable de actuar como iterador; es decir, devolver los diferentes elementos de una colección para la clase asociada. En este caso devolveremos los diferentes objetos de la clase Direccion asociados a una instancia de Persona. De hecho, el Iterador es el responsable de realizar todo el trabajo, moviendo o avanzando el cursor sobre el siguiente elemento y también devolviendo el objeto o valor cada vez que se solicite.

Creamos una nueva clase y la llamaremos IteradorPersona. En esta ocasión, en el Panel Inspector asociado nos encargaremos de pulsar sobre el botón Choose; asociado a la etiqueta Interfaces, acción que desplegará una ventana mostrando todas las Interfaces de Clase disponibles. Marcamos la casilla de verificación Xojo.Core.Iterator y confirmamos la selección.

Como consecuencia de la acción el IDE añadirá los métodos definidos por la interfaz de clase seleccionada:

  • MoveNext. La documentación indica que se invoca este método cada vez antes de llamar al método Value para obtener un nuevo valor. De hecho, en la implementación deberíamos de escribir el código, por tanto, que fuese incrementando el valor de un contador, e incluso verificar si dicho contador ha sobrepasado la cantidad de posibles elementos que puede devolver el iterador. Es decir, hemos de añadir una nueva Propiedad contador a nuestra clase encargada de esta tarea e inicializarla con el valor -1.
  • Value. Este es el método que se invocará cada vez que sea preciso obtener un nuevo valor. En nuestro ejemplo devolviendo un nuevo objeto Direccion de entre los disponibles.

Por tanto, la implementación del método MoveNext podría ser la siguiente en una primera aproximación:

contador = contador + 1

Ahora bien, la signatura del método y la documentación de la interfaz de clase indica que el método ha de devolver un valor booleano True mientras aun queden elementos por los que iterar, y False en el caso de que se haya superado la cantidad de elementos por los que se puede iterar, en cuyo caso deberíamos de lanzar una excepción para indicarlo.

Por ejemplo, nuestra clase podría comprobar que el contador no excede la cantidad de elementos almacenados por el Array Direcciones y devolver el booleano en consecuencia.

Pero antes de realizar la implementación final del método MoveNext nos encargaremos de rellenar algunos huecos. El primero será el de añadir algunas propiedades más necesarias:

  • maxDirecciones As Integer. Será la responsable de guardar la cantidad de elementos contenidos en el Array Direcciones.
  • source As Persona. Se encargará de guardar la referencia al objeto Persona sobre el que tiene que iterar.
  • La propiedad calculada valido As Boolean. Utilizaremos el método Get de esta propiedad calculada para saber si se ha modificado el Array de Direcciones durante la iteración del objeto, devolviendo False en el caso de que así sea. De hecho, el código del método Get será el siguiente:

Return if(source.direcciones.Ubound = maxDirecciones, True, false)

A continuación añadiremos un método Constructor a la clase, con la signatura:

Constructor( value As Persona ) y el siguiente código:

source = value

maxDirecciones = value.direcciones.Ubound

Es decir, inicializamos el valor de las propiedades declaradas previamente con la referencia al objeto Persona sobre el que vamos a iterar, así como la cantidad de elementos que incluye el Array en el momento de inicializar la clase; por tanto, la cantidad de elementos original que nos servirá para comprobar si se ha modificado durante la iteración.

Con estos pasos realizados, ya podemos volver al método MoveNext; y escribir la implementación final del método:

If valido Then
contador = contador + 1
Return If(contador <= source.direcciones.Ubound, True, False)
Else
Dim e As New Xojo.Core.IteratorException
e.Reason = "Datos no validos"
Raise e
End If

En la implementación del método Value también comprobaremos previamente que el objeto no se ha modificado durante la iteración, devolviendo el valor correspondiente:

If valido Then
Return source.direcciones(contador)
Else
Dim e As New Xojo.Core.IteratorException
e.Reason = "Datos no validos"
Raise e
End If

Como puedes observar, el método Value se encarga de devolver el item de Direccion en el Array de Persona, utilizando el valor de la propiedad Contador como índice.

Crear una clase Iterable

Ahora es el momento de crear la clase iterable propiamente dicha, para ello añadiremos una nueva clase al proyecto utilizando como nombre IterablePersona e indicando que se trata de una subclase de Persona, de modo que hemos de escribir dicho tipo de dato en el campo Super del Panel Inspector. Adicionalmente, esta clase será la que implemente la interfaz de clase Xojo.Core.Iterable, de modo que pulsaremos sobre el botón Choose asociado a la etiqueta Interfaces para seleccionar y confirmar la adición de los métodos. En realidad, sólo uno: GetIterator() As Xojo.Core.Iterator. El código a implementar en dicho método será el siguiente:

Dim it As New iteradorPersona(Self)
Return it

Con ello ya tenemos toda la definición que necesitamos para la subclase: obtenemos una nueva instancia de IteradorPersona pasando el objeto iterable (subclase de Persona) como parámetro, ¡y listo! Por último, devolvemos el iterador obtenido y que ya será el utilizado por el bucle For Each… Nex.

Poniendo todo en práctica

Armados con todas las clases necesarias para nuestro ejemplo, es el momento de ponerlo todo en práctica. Para ello, utilizaremos un ListBox en la ventana (Window1) y que será sobre el cual añadiremos tantas filas como direcciones asociadas a la instancia de la clase Persona que utilizaremos en el ejemplo. Para ello, añade también un botón a la ventana, añade el manejador de evento Action y escribe el siguiente código en el Editor de Código resultante:

Dim p As New iterablePersona
p.nombre = "Juan"
p.primerApellido = "Sin Miedo"
p.segundoApellido = "De Nada"
Dim d As New Dirección
d.calle = "Juan de Urbieta"
d.numero = 6
d.piso = 1
p.direcciones.Append d
d = new Direccion
d.calle = "Mi calle bonita"
d.numero = 8
d.piso = 3
p.direcciones.Append d
d = new Direccion
d.calle = "Princesa"
d.numero = 5
d.piso = 5
p.direcciones.Append d
For Each s As Direccion In p
Listbox1.AddRow s.calle + ", " + s.numero.ToText + " Piso: " + s.piso.ToText
Next

Iterator en Xojo

Ejecuta el proyecto, pulsa el botón y observarás como nuestro bucle For Each… Next ha iterado por cada una de las direcciones asociadas al objeto Persona, siendo una forma más elegante, concisa y OOP si lo comparamos con el uso del bucle For… Next clásico en el que se hace necesaria la obtención del valor superior o límite de la iteración y el uso de la variable asociada como índice para obtener así cada uno de los objetos en los que estemos interesados.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *