Desenvolvimento

15 abr, 2019

Arquitetura e Desenvolvimento de software – Parte 15: Iterator

Publicidade

Fala, galera!

Vamos para mais uma parte da série sobre design patterns.

Hoje conheceremos o Iterator, um pattern bem simples e veremos exemplos em Java e C#.

O design pattern Iterator tem como motivação encapsular uma iteração. Ele depende de uma interface chamada Iterator, conforme pode ser observado no exemplo a seguir:

Exemplo de interface Iterator

O método hasNext() determina se existem mais elementos para serem iterados, tendo um retorno do tipo boolean. O método next() retorna o próximo objeto da iteração.

No exemplo acima, o método next() retorna object, mas poderíamos utilizar generics, retornando um tipo T, como no exemplo a seguir.

Exemplo da interface Iterator com generics

Agora que já temos a interface, podemos implementar iteradores para qualquer tipo de coleção de objetos, sejam matrizes, listas, hashtables, etc.

Para cada coleção de objetos que queira encapsular a iteração, cria-se uma implementação para a interface Iterator. Por exemplo, para encapsular uma iteração para tarefas, teríamos a implementação a seguir:

Implementação da interface Iterator para Tarefas

Um método que poderia fazer parte de nossa implementação é o “remove” (para remover objetos da coleção), mas este é um método opcional.

A definição básica do Iterator é dada como:

  • “O pattern Iterator provê uma forma de acessar sequencialmente os elementos de um objeto agregado sem expor sua representação subjacente”.

Detalhando, o pattern Iterator permite acessar, um a um, elementos de um objeto agregador, mesmo sem saber como eles estão sendo representados. Assim, não precisamos conhecer se a coleção de objetos está num ArrayList, HashTable, Dictionary, ou qualquer outro tipo.

Além disso, o pattern Iterator assume a responsabilidade de acessar sequencialmente os elementos e transfere essa tarefa para o objeto Iterador. Dessa forma, o objeto agregador tem a sua interface e implementação simplificadas.

Vamos à ficha resumo do pattern, onde através de alguns exemplos poderemos compreender melhor como ele funciona e onde aplicar.

Ficha resumo

  • Nome: Iterator
  • Objetivo/intenção: Encapsular em um objeto a lógica de uma iteração, tornando o cliente mais simples, baseando-se em uma interface
  • Motivação: Prover acesso à iteração de uma coleção de elementos sem expor sua representação detalhada, de forma que o código apenas conheça a interface para iteração e o retorno esperado
  • Aplicabilidade: Utilizamos o pattern quando começamos a ter diversas coleções com tipos de iterações diferentes. Para poder reaproveitar essa lógica e reduzir a quantidade de código duplicado, encapsulamos ela em um Iterator e utilizamos onde for necessário. Uma vez que essa lógica de iteração mude, basta alterar na implementação específica do Iterator
  • Estrutura: No desenho abaixo temos um exemplo UML de aplicação do pattern, já aplicando Generics. Caso seja uma linguagem não suportada, apenas retiramos o Generics e usamos um retorno do tipo Object, por exemplo
UML do pattern Iterator, utilizando Generics.
  • Consequências: o pattern Iterator facilita nossa vida isolando a lógica ou forma de iterar um determinado tipo de coleção, evitando, inclusive, duplicação de código ou complexidade em várias partes do código, mas temos um aumento na estrutura do projeto, devido a Interface e diferentes implementações para as iterações, aumentando o número de classes no projeto
  • Implementações: abaixo temos um exemplo de código em C# para o pattern. Neste exemplo vamos utilizar a interface IEnumerator, já provida pelo .NET Framework para fazer a implementação. Ela é bem semelhante em estrutura e em relação a interface Iterator que vimos anteriormente com Java.
public class Pessoa {
    public string Nome { get; set; }
    public string Email { get; set; }
}

public class PessoaEnumerator : IEnumerator {
    private int _position;
    private ArrayList _arr = new ArrayList();
    
    public PessoaEnumerator()
    {
        _position = -1;
        _arr.Add(new Pessoa("Teste", "teste@provedor.com"));
        _arr.Add(new Pessoa("Teste2", "teste2@provedor2.com"));
        _arr.Add(new Pessoa("Fulano", "fulano@gmail.com"));
    }
    
    public bool MoveNext()
    {
        _position++;
        return (_position < _arr.Count);        
    }
    
    public Pessoa Current
    {
        get
        {
            return _arr[_position];
        }
    }
    
    public void Reset()
    {
        _position = -1;
    }
}
  • Usos conhecidos: muito utilizado em sistemas maiores e complexos, onde temos iteração por coleções diferentes, de formas diferentes sendo reutilizados em diversos módulos do sistema.
  • Padrões relacionados: Composite e Factory Method. Muitas vezes utilizamos o Factory Method para construção de iterators com as subclasses corretas.

Concluindo e avisos extras

Este foi o padrão Iterator, relativamente simples e que facilita bastante, encapsulando a lógica para percorrermos coleções em nosso sistema, evitando ainda a duplicação de código.

Na próxima parte da série abordaremos o pattern State.

Recomendo que sigam o canal do Developers-BR no YouTube. Sempre temos lives acontecendo sobre temas de mercado, e logo mais iniciarei com conteúdo sobre NodeJS e Azure lá. Basta acessar o canal, se inscrever, e não esquecer de clicar no sininho para não perder nada.

Um abraço a todos e até a próxima!