Skip to content
SP StackPractices
beginner

Patrón Iterator

Proporciona una forma de acceder a los elementos de una colección secuencialmente sin exponer su representación subyacente. Un patrón de comportamiento para recorrido.

Temas: design

Patrón Iterator

Visión General

El Patrón Iterator es un patrón de diseño de comportamiento que proporciona una forma de acceder a los elementos de un objeto agregado secuencialmente sin exponer su representación subyacente. Separa la lógica de recorrido de la colección misma, permitiendo múltiples recorridos simultáneos y diferentes estrategias de recorrido.

Cuándo Usarlo

Usa el Patrón Iterator cuando:

  • Necesitas recorrer una colección sin exponer su estructura interna
  • Quieres soportar múltiples algoritmos de recorrido (hacia adelante, hacia atrás, filtrado)
  • Necesitas permitir que múltiples clientes recorran simultáneamente
  • Quieres una interfaz uniforme para recorrer diferentes tipos de colecciones
  • La representación interna de la colección puede cambiar

Solución

Python

class BookCollection:
    def __init__(self):
        self._books = []

    def add(self, book: str):
        self._books.append(book)

    def __iter__(self):
        return iter(self._books)

    def reverse_iter(self):
        return reversed(self._books)

# Uso
collection = BookCollection()
collection.add("Design Patterns")
collection.add("Clean Code")
collection.add("Refactoring")

# Iteración hacia adelante (protocolo de iterador incorporado)
for book in collection:
    print(book)

# Iteración inversa
for book in collection.reverse_iter():
    print(book)

JavaScript

class BookCollection {
  constructor() {
    this.books = [];
  }

  add(book) {
    this.books.push(book);
  }

  *[Symbol.iterator]() {
    for (const book of this.books) {
      yield book;
    }
  }

  *reverseIterator() {
    for (let i = this.books.length - 1; i >= 0; i--) {
      yield this.books[i];
    }
  }
}

// Uso
const collection = new BookCollection();
collection.add("Design Patterns");
collection.add("Clean Code");
collection.add("Refactoring");

// Hacia adelante
for (const book of collection) {
  console.log(book);
}

// Inverso
for (const book of collection.reverseIterator()) {
  console.log(book);
}

Java

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class BookCollection implements Iterable<String> {
    private final List<String> books = new ArrayList<>();

    public void add(String book) {
        books.add(book);
    }

    @Override
    public Iterator<String> iterator() {
        return books.iterator();
    }

    public Iterator<String> reverseIterator() {
        return new ReverseIterator<>(books);
    }
}

class ReverseIterator<T> implements Iterator<T> {
    private final List<T> list;
    private int index;

    public ReverseIterator(List<T> list) {
        this.list = list;
        this.index = list.size() - 1;
    }

    @Override
    public boolean hasNext() {
        return index >= 0;
    }

    @Override
    public T next() {
        return list.get(index--);
    }
}

// Uso
BookCollection collection = new BookCollection();
collection.add("Design Patterns");
collection.add("Clean Code");
collection.add("Refactoring");

for (String book : collection) {
    System.out.println(book);
}

Explicación

El Patrón Iterator tiene dos roles:

  • Agregado (BookCollection): La colección que contiene los elementos
  • Iterator: Proporciona acceso secuencial a los elementos sin exponer los internals de la colección

Los lenguajes modernos integran profundamente los iteradores — los protocolos __iter__ de Python, Symbol.iterator de JavaScript, y las interfaces Iterable/Iterator de Java son todos ejemplos de este patrón.

Variantes

VarianteDescripciónCaso de Uso
Iterator ExternoEl cliente controla el recorrido (next(), hasNext())Flexible, control explícito
Iterator InternoLa colección aplica una función a cada elemento (forEach)Código de cliente más simple
Iterator InversoRecorre en orden inversoPilas, historial de undo
Iterator de FiltroSalta elementos que no coinciden con un predicadoBúsqueda, filtrado

Buenas Prácticas

  • Usa protocolos de iterador nativos del lenguaje cuando estén disponibles en lugar de clases personalizadas
  • Lanza excepciones en llamadas next() inválidas para fallar rápido
  • Soporta remove() solo cuando sea semánticamente válido y documenta claramente
  • Haz los iteradores fail-fast si la colección se modifica durante la iteración
  • Documenta si el orden de iteración está garantizado o es arbitrario

Errores Comunes

  • Exponer la colección interna directamente en lugar de usar un iterador
  • No manejar la modificación concurrente durante la iteración, llevando a comportamiento indefinido
  • Implementar clases de iterador complejas cuando un simple generador o comprensión es suficiente
  • Crear iteradores que no implementan el protocolo de iterador nativo del lenguaje
  • Olvidar resetear el estado del iterador, causando comportamiento inesperado en reuso

Preguntas Frecuentes

P: ¿Necesito implementar el Patrón Iterator manualmente? R: Raramente. La mayoría de los lenguajes proporcionan soporte de iterador incorporado. Solo implementa un iterador personalizado cuando necesites un recorrido no estándar (ej. recorrido de árbol, recorrido de grafo, o iteración filtrada).

P: ¿Cuál es la diferencia entre Iterator y Visitor? R: Iterator recorre elementos. Visitor realiza operaciones sobre elementos. A menudo se usan juntos: un iterador camina la estructura, y un visitor procesa cada elemento.