El patrón Iterator

El patrón Iterator

En Java como en cualquier lenguaje de programacion moderno orientado a objetos, existen multitud de tipos de listas y colecciones. Algunas de ellas permiten añadir punteros nulos, otras relacionan una clave a un valor y otras tienen una lógica interna que permiten comportamientos muy particulares y las hacen idóneas para según qué escenarios. Sin embargo todas ellas necesitarán eventualemente recorrer sus elementos, bien sea para ordenarlos o para ejecutar una acción sobre cada uno de ellos.

Evidentemente esto no es un problema en sí mismo, salvo que existan multitud de colecciones de distintos tipos que tenga que manejar un solo objeto. Si hacemos caso omiso a las estrechas dependencias que se crean entre las clases, no supone mayor problema, pero los patrones de diseño nos obligan a pensar en el largo plazo, y sabemos que una clase que depende de muchas implementaciones concretas (en este caso tipo de colecciones) puede ser un quebradero de cabeza. La solución que se nos puede pasar de primeras por la cabeza es la abstracción. En java por ejemplo, todas las colecciones salvo los mapas, heredan de la interfaz Collection, pero eso tampoco nos ayuda demasiado, ya que agrupaciones como los arrays o los mapas no heredan de Collection.

Se podría decir que java nos da una solución de serie, que es la de implementar en una clase la interfaz Iterator, de forma que no tengamos que saber qué tipo de coleccion contiene la clase, sino que es iterable y por tanto podemos navegar por los elementos que contiene.

Sin embargo, no puede ser tan fácil. Existe un principio de diseño que nos dice que hay que encapsular aquel código que depende de un único actor. Dicho de otra forma: debemos aislar el código que sólo tenga un motivo para cambiar. Fíjate que ahora la clase que contiene las colecciones tiene dos razones para cambiar, por ejemplo la forma de manejar los elementos y la forma de recorrerlos. Necesitaremos por tanto desarrollar una clase que cumpla este cometido concreto, implementando la interfaz Iterator.

Imaginemos por ejemplo una tienda de ropa con distintos proveedores. Cada proveedor tiene una forma distinta de organizar su ropa. Un proveedor la almacena en un Array y otro la almacena en un ArrayList. Ambas formas de organizar las prendas impican una forma distinta para iterar sobre ellas.

Como dueños de un comercio necesitamos acceder a la ropa de esos proveedores, pero tenemos el problema de depender de (por el momento) dos tipos de colecciones concretas. Es decir, si obtenermos las prendas mediante getters obtendremos en cada caso un array y una lista respectivamente. No queremos depender de una forma de iterar la ropa concreta para cada proveedor, por lo que creamos un objeto que se encargue de abstraer esta funcionalidad y que las tiendas (sus clientes) puedan usar. Este objeto deberá ser creado por los propios proveedores a traves de un método que devuelva un objeto iterable.

Si vemos detenidamente el diagrama UML anterior, podemos apreciar rápidamente ciertas formas de abstraer al cliente de las implementaciones concretas de los proveedores. Hemos dicho que todos los proveedores tenían que «entregar» un objeto de tipo Iterator al cliente, por lo que somos capaces de generalizar este comportamiento y crear una interfaz para todos los proveedores. De esta forma no dependemos de implementaciones concretas en el cliente, sino de una abstraccion: el Proveedor.
Ahora tenemos un cliente desacoplado tanto de los proveedores como del tipo de colecciones que usan.