Iterator
Introdução
Seção intitulada “Introdução”O Iterator é um padrão de projeto comportamental que permite percorrer os elementos de uma coleção sem expor os detalhes de como essa coleção está organizada internamente.
Na prática, ele separa a responsabilidade de armazenar dados da responsabilidade de percorrer dados. Assim, o cliente consegue navegar por listas, pilhas, árvores ou outras estruturas usando uma interface comum.
Esse padrão existe para reduzir acoplamento, facilitar a troca de algoritmos de travessia e evitar que a lógica de iteração fique espalhada pelo sistema.
Problema
Seção intitulada “Problema”Imagine um sistema acadêmico que precisa trabalhar com diferentes coleções de dados:
- uma lista de alunos matriculados
- uma pilha de solicitações recentes
- uma árvore de disciplinas e pré-requisitos
- uma coleção filtrada de notificações
Cada estrutura pode exigir um jeito diferente de travessia. Em alguns casos, a navegação é linear. Em outros, pode ser em profundidade, em largura ou até baseada em filtros.
Se o código cliente precisar conhecer a estrutura interna de cada coleção, surgem vários problemas:
- aumento do acoplamento com classes concretas
- duplicação de lógica de travessia
- dificuldade para trocar o tipo de coleção
- risco de expor detalhes internos desnecessários
- maior esforço para adicionar novos modos de iteração
O problema central é este: o cliente quer acessar os elementos, mas não deveria depender de como a coleção os armazena ou percorre.
Solução
Seção intitulada “Solução”O Iterator resolve isso extraindo a lógica de travessia para um objeto separado: o iterador.
Em vez de o cliente percorrer diretamente a coleção, ele pede a ela um iterador e usa operações simples para avançar.
Uma forma de entender a solução é esta:
- a coleção continua responsável por armazenar os elementos
- o iterador passa a ser responsável por controlar a travessia
- o cliente conversa com a interface do iterador, não com a estrutura interna
- diferentes iteradores podem oferecer diferentes estratégias de navegação
- múltiplos iteradores podem percorrer a mesma coleção ao mesmo tempo, cada um com seu próprio estado
Com isso, a coleção fica mais coesa, o cliente fica menos acoplado e a iteração passa a ser reutilizável e extensível.
Analogia
Seção intitulada “Analogia”Pense em uma biblioteca universitária. Os livros estão organizados em estantes, corredores e seções. Um estudante não precisa conhecer toda a lógica interna de catalogação para consultar os livros. Ele pode usar um catálogo, um mapa de busca ou a ajuda de um bibliotecário.
Cada uma dessas opções oferece uma forma de percorrer a mesma coleção de livros. A biblioteca continua sendo a coleção. O catálogo, o mapa ou o bibliotecário funcionam como iteradores diferentes sobre esse mesmo conjunto.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use Iterator quando:
- a coleção possui estrutura interna que você não quer expor
- existem vários modos possíveis de travessia
- o cliente precisa percorrer diferentes coleções por uma interface comum
- você quer reduzir duplicação de código de iteração
- a travessia precisa manter estado independente por execução
Exemplos reais em software:
- percorrer resultados paginados de uma API
- navegar por árvore de menus ou categorias
- iterar sobre componentes de uma interface gráfica
- processar nós de uma árvore sintática
- percorrer conexões de um grafo social ou acadêmico
Anti-padrão / Mau uso
Seção intitulada “Anti-padrão / Mau uso”Iterator pode ser mal aplicado quando a equipe cria iteradores próprios para coleções muito simples sem ganho claro de legibilidade ou flexibilidade.
Erros frequentes:
- criar abstrações demais para uma lista trivial
- misturar regras de negócio dentro do iterador
- fazer o iterador depender de detalhes externos demais
- expor internamente a coleção mesmo dizendo que há encapsulamento
- usar o padrão sem necessidade, apenas por seguir catálogo
As consequências costumam ser aumento de complexidade, excesso de classes e dificuldade de manutenção. O Iterator faz mais sentido quando realmente existe valor em separar travessia de armazenamento.
Como implementar
Seção intitulada “Como implementar”Uma implementação incremental pode seguir estes passos:
- identifique a coleção que precisa ser percorrida de maneira controlada
- defina uma interface de iterador com operações como
temProximo()eproximo() - implemente um iterador concreto que mantenha o estado da travessia
- faça a coleção expor um método para criar iteradores
- ajuste o cliente para usar o iterador em vez de acessar a estrutura diretamente
- se necessário, crie novos iteradores para percursos alternativos
Antes de implementar, vale perguntar: o problema está na coleção em si ou na variedade de formas de percorrê-la? Se a travessia estiver atrapalhando o design, Iterator é um bom candidato.
Exemplo em código
Seção intitulada “Exemplo em código”No exemplo abaixo, uma coleção de disciplinas expõe um iterador simples para percorrer seus elementos sem revelar a estrutura interna.
import java.util.ArrayList;import java.util.List;
interface IteradorDisciplina { boolean temProximo(); String proximo();}
class GradeCurricular { private final List<String> disciplinas = new ArrayList<>();
public void adicionar(String disciplina) { disciplinas.add(disciplina); }
public IteradorDisciplina criarIterador() { return new GradeCurricularIterador(disciplinas); }}
class GradeCurricularIterador implements IteradorDisciplina { private final List<String> disciplinas; private int posicaoAtual = 0;
public GradeCurricularIterador(List<String> disciplinas) { this.disciplinas = disciplinas; }
@Override public boolean temProximo() { return posicaoAtual < disciplinas.size(); }
@Override public String proximo() { if (!temProximo()) { throw new IllegalStateException("Nao ha mais disciplinas."); }
return disciplinas.get(posicaoAtual++); }}Nesse código, GradeCurricular cuida dos dados, enquanto GradeCurricularIterador cuida da navegação. O cliente não precisa saber se as disciplinas estão em uma lista, árvore ou outra estrutura.
- reduz o acoplamento entre cliente e coleção concreta
- centraliza a lógica de travessia
- permite múltiplos algoritmos de iteração
- facilita percursos paralelos sobre a mesma coleção
- melhora extensibilidade sem alterar o cliente
Contras
Seção intitulada “Contras”- adiciona mais classes e interfaces ao design
- pode ser excessivo para coleções muito simples
- pode introduzir alguma indireção extra
- exige cuidado com estado interno do iterador
- pode complicar depuração se houver muitos tipos de percurso
Relações com outros padrões/conceitos
Seção intitulada “Relações com outros padrões/conceitos”O Iterator se relaciona bem com outros padrões e ideias:
- Composite: uma estrutura em árvore costuma precisar de iteradores para percorrer seus nós
- Factory Method: a coleção pode usar um método-fábrica para retornar diferentes iteradores
- Memento: pode ser usado para salvar e restaurar o estado de uma iteração
- Visitor: Visitor executa operações nos elementos; Iterator ajuda a percorrê-los
- Encapsulamento: o padrão reforça a ideia de esconder a representação interna da coleção
Essas relações mostram que o Iterator não serve para armazenar dados de modo diferente, mas para organizar melhor a forma de acesso aos elementos.
Implementações alternativas (quando aplicável)
Seção intitulada “Implementações alternativas (quando aplicável)”Algumas variações comuns incluem:
- iteradores externos, em que o cliente controla explicitamente a travessia
- iteradores internos, em que a coleção aplica uma função aos elementos
- iteradores em profundidade e em largura para árvores
- iteradores filtrados, paginados ou reversos
- uso de iteradores nativos da linguagem, como
Iteratorem Java ouIterable
Em muitos frameworks, a ideia do padrão já aparece embutida nas bibliotecas de coleção. Ainda assim, entender o padrão ajuda a projetar estruturas próprias quando o caso exige.
Exemplo completo
Seção intitulada “Exemplo completo”Agora veja um exemplo mais realista. Um sistema de aprendizagem precisa percorrer módulos de um curso de duas formas: na ordem padrão e na ordem inversa para revisão.
import java.util.ArrayList;import java.util.List;
interface IteradorModulo { boolean temProximo(); Modulo proximo();}
class Modulo { private final String titulo;
public Modulo(String titulo) { this.titulo = titulo; }
public String getTitulo() { return titulo; }}
class Curso { private final List<Modulo> modulos = new ArrayList<>();
public void adicionarModulo(Modulo modulo) { modulos.add(modulo); }
public IteradorModulo criarIteradorPadrao() { return new IteradorCursoPadrao(modulos); }
public IteradorModulo criarIteradorReverso() { return new IteradorCursoReverso(modulos); }}
class IteradorCursoPadrao implements IteradorModulo { private final List<Modulo> modulos; private int indice = 0;
public IteradorCursoPadrao(List<Modulo> modulos) { this.modulos = modulos; }
public boolean temProximo() { return indice < modulos.size(); }
public Modulo proximo() { return modulos.get(indice++); }}
class IteradorCursoReverso implements IteradorModulo { private final List<Modulo> modulos; private int indice;
public IteradorCursoReverso(List<Modulo> modulos) { this.modulos = modulos; this.indice = modulos.size() - 1; }
public boolean temProximo() { return indice >= 0; }
public Modulo proximo() { return modulos.get(indice--); }}
public class Main { public static void main(String[] args) { Curso curso = new Curso(); curso.adicionarModulo(new Modulo("Introducao")); curso.adicionarModulo(new Modulo("Colecoes")); curso.adicionarModulo(new Modulo("Iterator"));
IteradorModulo padrao = curso.criarIteradorPadrao(); while (padrao.temProximo()) { System.out.println("Ordem normal: " + padrao.proximo().getTitulo()); }
IteradorModulo reverso = curso.criarIteradorReverso(); while (reverso.temProximo()) { System.out.println("Revisao: " + reverso.proximo().getTitulo()); } }}Nesse exemplo, o mesmo objeto Curso oferece dois modos de travessia sem expor como os módulos estão armazenados. O cliente muda o comportamento apenas escolhendo outro iterador.