Implementação do Padrão de Projeto Iterator em Java

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.

Tags: java design patterns Iterator Object Oriented Programming Data Structures

Publicado em 7-16 20:02