Patrones de Diseño (XVII): Patrones de Comportamiento - Iterator

Continuando con los patrones de comportamiento, hoy vamos a ver el patrón Iterator, un patrón que nos permite recorrer los elementos de un conjunto sin importar cómo se representen internamente.

Para los que hayáis programado en Java este concepto os resultará familiar, ya que su librería estándar trae implementado este patrón. Todos los elementos de tipo lista poseén un método que devuelve un iterator, con el cual podemos trabajar cómodamente sobre colecciones.

Vamos a ver cómo se comporta este patrón revisando su plantilla y a estudiar un caso práctico:

Iterator

Nombre: Iterator

Problema y Contexto:

El patrón Iterator se usa en el contexto de las listas y conjuntos. Tenemos una serie de objetos que internamente trabajan con conjuntos de elementos y necesitamos manipularlos abstrayéndonos de cómo están implementados internamente.

De esta manera si por alguna razón de eficiencia o funcionalidad necesitáramos cambiar la implementación interna del conjunto de elementos, el resto de nuestro sistema seguiría funcionando sin problemas.

 

Se aplica cuando:

Debemos aplicar este patrón cuando tengamos que trabajar con objetos que internamente trabajan sobre un grupo de elementos y debamos poder manejar dichos elementos sin que un cambio en la implementación de la lista o cojunto afecte al sistema global.

Otra situación en la que este patrón resulta de utilidad es cuando los elementos de una lista pueden ser recorridos de diferentes maneras. Por ejemplo tenemos una lista de números la cual podemos recorrer secuencialmente e inversamente, pero nuestro sistema debe hacerlo de la misma manera, sin necesidad de utilizar índices ni variables adicionales.

 

Solución y Estructura:

La solución consiste en crear una interfaz Iterator que estandarice los métodos para tratar la colección de elementos. Esta interfaz definirá una serie de operaciones para manipular los elementos del conjunto, como puede ser next() para obtener el siguiente elemento, hasNext() para comprobar que sigue habiendo elementos en el conjunto, current() para obtener el elemento actual o first() para mover el cursor al primer elemento y a la obtener una referencia al mismo.

Para implementar la interfaz Iterator utilizaremos una clase ConcreteIterator que implemente dicha interfaz, la cual se encargará de controlar la posición del cursor y manejar los elementos según las operaciones definidas por la interfaz.

Para crear objetos Iterator utilizaremos otra interfaz llamada Aggregate, que se encargará de devolver objetos Iterator a partir de nuestros objetos que manejan colecciones. A su vez se necesita un ConcreteAgreggate para definir de qué manera se crea cada Iterador en particular.

 

La estructura es la siguiente:

(Ver imágen al comienzo)

 

Donde:

 

Iterator: Interfaz que define las operaciones que podemos realizar sobre una colección.

 

ConcreteIterator: Implementa las operaciones definidas por la interfaz Iterator.

 

Aggregate: Interfaz para crear objetos Iterator.

 

AgregadoConcreto: Implementa las operaciones que expone la interfaz Aggregate y devuelve una instacncio del iterador concreto en cuestión.

 

Consecuencias:

POSITIVAS:

  • Estandariza el tratado de elementos de listas con implementaciones independientes.
  • Permite variar el tratamiento de las listas modificando tan solo la clase que implementa el iterador.
  • Se pueden realizar múltiples recorridos simultáneos.

NEGATIVAS:

  • Aumenta la jerarquía de clases.

 

Patrones Relacionados: Composite, Factory Method y Memento.

 

Ejemplo:

Para comprender mejor el patrón Iterator, vamos a ver un ejemplo en el que tenemos 2 implementaciones de una lista de números, una como array nativo y la otra como ArrayList y pretendemos utilizarlas indistintamente sin cambiar el código del cliente. Vamos a ello:

// En primer lugar crearemos las interfaces Iterator
// y Aggregate respectivamente.

public interface MyIterator {
	public boolean hasNext();
	public T next();
	public T current();
	public T first();
}

public interface Aggregate {
	public MyIterator iterator();
}

// Ahora crearemos la implementación de los Aggregate, que
// internamente contendrán una subclase que implemente a Iterator

// Primero para los arrays nativos
public class ListaNumerosNativa implements Aggregate {
	
	private int[] list = {-1,2,-3,-4,5,6};

	public MyIterator iterator() {
		return NativeIterator(list);
	}

	private class NativeIterator implements MyIterator {
		private int[] list;
		private int index = 0;

		public NativeIterator(int[] list) {
			this.list = list;
		}

		public boolean hasNext() {
			return index < list.length;
		}

		public Integer next() {
			return hasNext() ? new Integer(list[++index]) : null;
		}

		public Integer current() {
			return new Integer(list[index]);
		}
		
		public Integer first() {
			index = 0;
			return new Integer(list[index]);
		}
	}	
}

// Ahora haremos lo propio para los ArrayList
public class ListaNumerosArrayList implements Aggregate {
	
	private ArrayList list = new ArrayList();

	public ListaNumerosArrayList() {
		list.add(new Integer(-1));
		list.add(new Integer(2));
		list.add(new Integer(-3));
		list.add(new Integer(-4));
		list.add(new Integer(5));
		list.add(new Integer(6));
	}

	public MyIterator iterator() {
		return ArrayListIterator(list);
	}

	private class ArrayListIterator implements MyIterator {
		private ArrayList list;
		private int index = 0;

		public ListaNumerosArrayList(ArrayList list) {
			this.list = list;
		}

		public boolean hasNext() {
			return index < list.size();
		}

		public Integer next() {
			return hasNext() ? list.get(++index) : null;
		}

		public Integer current() {
			return list.get(index);
		}
		
		public Integer first() {
			index = 0;
			return list.get(index);
		}
	}
}

// Por último, probaremos todo en el método main
public static void main(String[] args) {
	Aggregate lista;

	if(args[0].equals("nativa"))
		lista = new ListaNumerosNativa();
	else if(args[0].equals("arraylist"))
		lista = new ListaNumerosArrayList();
	else
		return;
	// Recorreremos la lista independientemente de su implementación interna
	MyIterator it = lista.iterator();
	while (it.hasNext())
	{
		System.out.println(it.next());
	}	
}

 

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP