Pular para o conteúdo
GoF/23 Padrões de Projeto Comportamentais
257
Comportamental escopo de objeto também: Cursor

Iterator

Iterador

Percorra uma coleção sem saber como ela é feita por dentro.

Complexidade simples Frequência de uso muito comum
D

Definição

Intenção — palavra por palavra, como o livro define

Fornecer uma maneira de acessar sequencialmente os elementos de um objeto agregado sem expor sua representação subjacente.

E

Exemplo e motivação

Analogia

Formas de visitar uma cidade

Você pode andar a esmo, seguir um app de mapas ou contratar um guia. Cada um é uma estratégia de percurso sobre a mesma cidade, e nenhuma exige que você entenda o plano diretor urbano. Trocar de guia não muda a cidade; e dois turistas podem percorrê-la ao mesmo tempo, cada um no seu ponto.

O problema

Coleções guardam dados de formas muito diferentes (array, lista ligada, árvore, grafo, páginas de uma API). Colocar todos os algoritmos de percurso dentro da coleção a incha e quebra sua responsabilidade principal — e o cliente que percorre acaba dependendo da estrutura interna.

A solução

Extraia o percurso para um objeto iterador separado, com uma interface uniforme (`próximo()`, `temPróximo()`). Cada iterador guarda sua própria posição, o que permite vários percursos simultâneos e independentes sobre a mesma coleção.

Sintomascomo reconhecer no seu código
  • O cliente acessando colecao.itensInternos[i] diretamente.
  • Métodos percorrerAssim() e percorrerAssado() inchando a classe da coleção.
Na práticaonde ele já existe
  • java.util.Iterator e todo o Java Collections Framework
  • Symbol.iterator, generators e for..of no JavaScript
  • __iter__ do Python; IEnumerable/IEnumerator do C#
D

Diagrama

Client«interface»Colecao+ criarIterador()«interface»Iterador+ proximo()+ temProximo()ArvoreBinariaEmOrdemEmLarguracria
Fig. 16 — Iterator herda / implementa cria usa / contém
Ver este diagrama sendo desenhado, traço a traço
Participantes
Iterator
Interface de percurso: próximo, tem próximo, valor atual.
ConcreteIterator
Implementa um algoritmo de percurso e guarda a posição.
Aggregate
Interface que declara um método para obter um iterador.
ConcreteAggregate
A coleção; devolve o iterador apropriado (Factory Method).
A

Aplicabilidade

Use quando
  • A estrutura interna da coleção é complexa e você quer escondê-la.
  • Você precisa de vários tipos de percurso sobre a mesma coleção.
  • Você quer uma interface de percurso uniforme sobre coleções diferentes.
  • Os dados são grandes demais para caber na memória (streams, paginação).
Evite quando
  • Você só percorre um array simples do começo ao fim.
C

Consequências

A favor
  • Limpa a coleção e o cliente (Responsabilidade Única).
  • Novos percursos e novas coleções entram independentemente (Aberto/Fechado).
  • Vários percursos simultâneos, cada um com sua posição.
  • Permite pausar e retomar o percurso; viabiliza sequências infinitas e preguiçosas.
Contra
  • Exagero para coleções simples — um `for` direto é mais claro.
  • Pode ser menos eficiente que percorrer a estrutura diretamente.
Apêndice

Código

Listagem 16 Dois percursos sobre a mesma árvore + paginação preguiçosa
class No { constructor(v, esq = null, dir = null) { Object.assign(this, { v, esq, dir }); } }

class Arvore {
  constructor(raiz) { this.raiz = raiz; }

  // Em JavaScript, iteradores são de primeira classe: geradores + Symbol.iterator.
  *emOrdem(no = this.raiz) {
    if (!no) return;
    yield* this.emOrdem(no.esq);
    yield no.v;
    yield* this.emOrdem(no.dir);
  }

  *emLargura() {
    const fila = [this.raiz];
    while (fila.length) {
      const no = fila.shift();
      if (!no) continue;
      yield no.v;
      fila.push(no.esq, no.dir);
    }
  }

  // O percurso padrão da coleção:
  [Symbol.iterator]() { return this.emOrdem(); }
}

const arvore = new Arvore(new No(8, new No(3, new No(1), new No(6)), new No(10, null, new No(14))));

console.log([...arvore.emOrdem()]);    // [1, 3, 6, 8, 10, 14]
console.log([...arvore.emLargura()]);  // [8, 3, 10, 1, 6, 14]
for (const v of arvore) { /* usa o percurso padrão, sem conhecer a estrutura */ }

// ── Iterador PREGUIÇOSO sobre uma API paginada ────────
async function* todosOsUsuarios(fetchPagina) {
  let pagina = 1, resultado;
  do {
    resultado = await fetchPagina(pagina++);   // só busca quando o consumidor pede
    yield* resultado.itens;
  } while (resultado.temMais);
}
// for await (const u of todosOsUsuarios(api)) { … }  ← milhões de itens, memória constante
Apêndice

Relações

costuma andar junto
Composite
Iterators são a forma natural de percorrer árvores Composite.
Factory Method
A coleção usa um Factory Method para devolver o iterador certo.
Memento
Um memento pode capturar o estado de um iterador para retomá-lo depois.
Visitor
Iterator percorre; Visitor decide o que fazer em cada nó.
Apêndice

Verificação

Questãoa resposta está marcada

Por que o estado do percurso fica no ITERADOR e não na coleção?

  1. Para permitir vários percursos simultâneos e independentes sobre a mesma coleção
  2. Porque coleções não podem ter campos mutáveis
  3. Para reduzir o consumo de memória da coleção
  4. Porque a linguagem exige isso

Se a posição morasse na coleção, dois laços aninhados sobre ela interfeririam um no outro. Iteradores independentes resolvem isso.