O padrão Iterator é fundamental no desenvolvimento de software quando lidamos com coleções de objetos. Sua principal finlaidade é fornecer uma maneira de acessar sequencialmente os elementos de um objeto agregado sem expor sua representação subjacente.
1. Construindo um Recipiente Dinâmico
Para entender a necessidade do Iterator, primeiro criamos um contêiner simples baseado em array que cresce dinamicamente. Diferente de um array estático, este objeto gerencia seu próprio redimensionamento.
public class VetorCustomizado {
private Object[] dados = new Object[10];
private int posicao = 0;
public void inserir(Object elemento) {
if (posicao == dados.length) {
Object[] novoArray = new Object[dados.length * 2];
System.arraycopy(dados, 0, novoArray, 0, dados.length);
dados = novoArray;
}
dados[posicao++] = elemento;
}
public int totalElementos() {
return posicao;
}
}
Uma classe de suporte simples para testes:
public class Produto {
private int codigo;
public Produto(int codigo) {
this.codigo = codigo;
}
@Override
public String toString() {
return "Produto ID: " + codigo;
}
}
2. Implementação com Lista Encadeada
Embora o array seja efciiente para acesso aleatório, uma lista encadeada possui uma estrutura interna completamente diferente, baseada em nós conectados por referências.
class No {
Object conteudo;
No proximo;
No(Object conteudo, No proximo) {
this.conteudo = conteudo;
this.proximo = proximo;
}
}
public class ListaSimples {
private No inicio = null;
private No fim = null;
private int contador = 0;
public void adicionar(Object o) {
No novoNo = new No(o, null);
if (inicio == null) {
inicio = novoNo;
fim = novoNo;
} else {
fim.proximo = novoNo;
fim = novoNo;
}
contador++;
}
public int tamanho() {
return contador;
}
}
3. Abstração e Polimorfismo
Para que o código cliente não dependa de uma implementação específica (array ou lista), definimos uma interface comum. Isso permite a substituição de uma estrutura por outra sem alterar a lógica de negócio.
public interface EstruturaDados {
void adicionar(Object obj);
int tamanho();
}
4. O Problema da Iteração Unificada
Mesmo com uma interface comum para adicionar elementos, a forma de percorrer um array é diferente de percorrer uma lista encadeada. O padrão Iterator resolve isso ao padronizar o mecanismo de travessia.
Definição das Interfaces do Padrão
public interface Iterador {
Object proximo();
boolean temProximo();
}
public interface EstruturaDados {
void adicionar(Object obj);
int tamanho();
Iterador criarIterador();
}
Implementação no Vetor Dinâmico
public class VetorCustomizado implements EstruturaDados {
private Object[] dados = new Object[10];
private int posicao = 0;
public void adicionar(Object obj) {
if (posicao == dados.length) {
Object[] novo = new Object[dados.length * 2];
System.arraycopy(dados, 0, novo, 0, dados.length);
dados = novo;
}
dados[posicao++] = obj;
}
public int tamanho() { return posicao; }
public Iterador criarIterador() {
return new IteradorVetor();
}
private class IteradorVetor implements Iterador {
private int indiceAtual = 0;
public boolean temProximo() {
return indiceAtual < posicao;
}
public Object proximo() {
return dados[indiceAtual++];
}
}
}
Implementação na Lista Encadeada
public class ListaSimples implements EstruturaDados {
private No inicio = null;
private No fim = null;
private int contador = 0;
public void adicionar(Object o) {
No novo = new No(o, null);
if (inicio == null) {
inicio = novo;
} else {
fim.proximo = novo;
}
fim = novo;
contador++;
}
public int tamanho() { return contador; }
public Iterador criarIterador() {
return new IteradorLista();
}
private class IteradorLista implements Iterador {
private No atual = inicio;
public boolean temProximo() {
return atual != null;
}
public Object proximo() {
Object dado = atual.conteudo;
atual = atual.proximo;
return dado;
}
}
}
5. Utilização Prática
Com o padrão implementado, podemos trocar a estrutura de dados sem alterar o loop de repetição que processa os elementos.
public class Sistema {
public static void main(String[] args) {
// Pode ser substituído por new VetorCustomizado() livremente
EstruturaDados colecao = new ListaSimples();
for (int i = 0; i < 5; i++) {
colecao.adicionar(new Produto(i));
}
Iterador it = colecao.criarIterador();
while (it.temProximo()) {
System.out.println(it.proximo());
}
}
}
Ao desacoplar o algoritmo de iteração da estruutra interna da coleção, o código torna-se mais flexível, facilitando a manutenção e a expansão do sistema com novas formas de armazenamento de dados.