Introducción

El patrón de diseño Iterator es un patrón de comportamiento que proporciona una forma de acceder secuencialmente a los elementos de un objeto agregado sin exponer su representación subyacente. Este patrón es útil cuando se necesita recorrer una colección de objetos de manera uniforme, independientemente de su estructura interna.

Conceptos Clave

  • Iterador: Un objeto que permite recorrer una colección de elementos.
  • Colección: Un objeto que contiene múltiples elementos y puede proporcionar un iterador para recorrerlos.
  • Interfaz de Iterador: Define las operaciones necesarias para recorrer la colección (por ejemplo, next(), hasNext()).

Estructura del Patrón Iterator

El patrón Iterator se compone de los siguientes componentes:

  1. Iterador (Iterator): Define la interfaz para acceder y recorrer los elementos.
  2. Iterador Concreto (ConcreteIterator): Implementa la interfaz del iterador y mantiene la posición actual en la iteración.
  3. Colección (Aggregate): Define la interfaz para crear un iterador.
  4. Colección Concreta (ConcreteAggregate): Implementa la interfaz de la colección y devuelve una instancia del iterador concreto.

Diagrama UML

+-------------------+     +-------------------+
|      Client       |     |    Iterator       |
+-------------------+     +-------------------+
| - aggregate: List |     | + hasNext(): bool |
| - iterator: Iter  |<----| + next(): Element |
+-------------------+     +-------------------+
        |                         ^
        |                         |
        v                         |
+-------------------+     +-------------------+
|  ConcreteIterator |     |   Aggregate       |
+-------------------+     +-------------------+
| - index: int      |     | + createIterator()|
| + hasNext(): bool |     +-------------------+
| + next(): Element |                ^
+-------------------+                |
        ^                            |
        |                            |
+-------------------+     +-------------------+
| ConcreteAggregate |     | ConcreteAggregate |
+-------------------+     +-------------------+
| - items: List     |     | + createIterator()|
+-------------------+     +-------------------+

Ejemplo Práctico en Python

Vamos a implementar un ejemplo simple del patrón Iterator en Python. Imaginemos que tenemos una colección de números y queremos iterar sobre ellos.

Código

Interfaz del Iterador

from abc import ABC, abstractmethod

class Iterator(ABC):
    @abstractmethod
    def has_next(self) -> bool:
        pass

    @abstractmethod
    def next(self):
        pass

Iterador Concreto

class NumberIterator(Iterator):
    def __init__(self, numbers):
        self._numbers = numbers
        self._position = 0

    def has_next(self) -> bool:
        return self._position < len(self._numbers)

    def next(self):
        if self.has_next():
            number = self._numbers[self._position]
            self._position += 1
            return number
        else:
            raise StopIteration

Interfaz de la Colección

from abc import ABC, abstractmethod

class Aggregate(ABC):
    @abstractmethod
    def create_iterator(self) -> Iterator:
        pass

Colección Concreta

class NumberCollection(Aggregate):
    def __init__(self):
        self._numbers = []

    def add_number(self, number):
        self._numbers.append(number)

    def create_iterator(self) -> Iterator:
        return NumberIterator(self._numbers)

Uso del Patrón Iterator

if __name__ == "__main__":
    collection = NumberCollection()
    collection.add_number(1)
    collection.add_number(2)
    collection.add_number(3)

    iterator = collection.create_iterator()

    while iterator.has_next():
        number = iterator.next()
        print(number)

Explicación del Código

  1. Interfaz del Iterador: Define los métodos has_next() y next().
  2. Iterador Concreto: Implementa la interfaz del iterador y mantiene la posición actual en la colección.
  3. Interfaz de la Colección: Define el método create_iterator().
  4. Colección Concreta: Implementa la interfaz de la colección y devuelve una instancia del iterador concreto.
  5. Uso del Patrón Iterator: Crea una colección de números, agrega números a la colección y usa el iterador para recorrer los números.

Ejercicio Práctico

Ejercicio

Implementa un iterador para una colección de nombres. La colección debe permitir agregar nombres y el iterador debe recorrer los nombres en orden.

Solución

class NameIterator(Iterator):
    def __init__(self, names):
        self._names = names
        self._position = 0

    def has_next(self) -> bool:
        return self._position < len(self._names)

    def next(self):
        if self.has_next():
            name = self._names[self._position]
            self._position += 1
            return name
        else:
            raise StopIteration

class NameCollection(Aggregate):
    def __init__(self):
        self._names = []

    def add_name(self, name):
        self._names.append(name)

    def create_iterator(self) -> Iterator:
        return NameIterator(self._names)

if __name__ == "__main__":
    collection = NameCollection()
    collection.add_name("Alice")
    collection.add_name("Bob")
    collection.add_name("Charlie")

    iterator = collection.create_iterator()

    while iterator.has_next():
        name = iterator.next()
        print(name)

Retroalimentación

  • Error Común: Olvidar incrementar la posición en el iterador. Asegúrate de incrementar _position después de obtener el elemento actual.
  • Consejo: Siempre verifica si hay más elementos antes de llamar a next() para evitar excepciones.

Conclusión

El patrón Iterator es una herramienta poderosa para recorrer colecciones de manera uniforme sin exponer su estructura interna. Al implementar este patrón, se puede mejorar la flexibilidad y la reutilización del código. En el siguiente tema, exploraremos el patrón Mediator, que facilita la comunicación entre objetos sin necesidad de referencias directas entre ellos.

© Copyright 2024. Todos los derechos reservados