C# - Padrão Comportamental Gof - Iterator
Neste artigo vou apresentar o padrão comportamental Gof Iterator. |
Segundo a
definição da Gang Of Four(GoF), o padrão
Iterator
fornece uma maneira de acessar os elementos de um objeto agregado (coleção)
sequencialmente sem expor sua representação subjacente, ou seja, este padrão
permite o acesso sequencial a elementos de uma coleção sem expor a lógica
interna.
A ideia principal
deste padrão é extrair o comportamento transversal de uma coleção para um objeto
separado chamado de iterador.
Assim este padrão define um objeto separado (iterador) que encapsula o acesso e
a travessia de um objeto agregado. Isso significa que, usando o padrão Iterator , podemos acessar os elementos de uma coleção de forma sequencial, sem
a necessidade de conhecer sua representação interna.
Lembrando que n a programação orientada a objetos, as coleções se comportam como
um container que contém muitos objetos.
A maioria das coleções armazena seus elementos em listas simples. No entanto,
algumas delas são baseados em pilhas, árvores, gráficos e outras estruturas de
dados complexas.
O padrão Iterator encapsula um iterador que é usado para percorrer o contêiner
de objetos e acessar os elementos deste contêiner.
Iterator : Exemplo
Vejamos um exemplo para entender melhor o padrão Iterator.
Vamos imaginar uma
coleção qualquer que armazena objetos como em um contêiner :
Esta coleção pode ser um Array, um ArrayList, uma List, Dictionary, etc.
A estrutura de dados da List será diferente da estrutura de dados da
ArrayList
que será diferente do Dictionary e o mecanismo de armazenamento de
List será diferente do mecanismo de armazenamento de Array e também
será diferente de
ArrayList.
Portanto, o principal uso do padrão Iterator é acessar os elementos (object1, object2, object3 e object4) de uma coleção (ou seja, List, ArrayList e Array) de maneira sequencial. Para isso ele usa iteradores. (usamos iteradores com bastante frequência)
Qualquer controle remoto que usamos funciona da mesma forma , basta pegar o controle e começar a pressionar as teclas para cima ou para baixo para frente ou para trás. Esse é o trabalho que um iterador faz.
Na linguagem C# podemos iterar facilmente uma coleção usando um laço for ou foreach (na maioria das coleções o foreach é mais indicado)
foreach(var elemento in colecao) { //...elemento } |
Neste código temos
que o laço for-each acessa sequencialmente os elementos da coleção sem
expor a lógica interna, e isso é feito graças a implementação do padrão
iterator disponível na plataforma .net
Mas aqui, dependendo do tipo de coleção, se você quiser usar uma estrutura de
dados diferente, será necessário alterar o código do cliente. Não seria bom
fornecer uma interface uniforme para percorrer diferentes tipos de coleções
especificas ?
Podemos então fazer uma implementação do padrão Iterator
e assim criar um iterador customizado. Um exemplo de código seria o
seguinte:
foreach (var elemento = iterator.First(); !iterator.IsDone(); elemento=iterator.Next()) { //...iterator.CurrentItem(); } |
Assim podemos usar o padrão iterator para implementações quando tivermos uma situação que a implementação da plataforma .NET não nos atender.
A plataforma .NET oferece uma implementação do padrão Iterator para trabalhar com coleções e listas.
Padrão Iterator |
Plataforma .NET |
Aggregate |
IEnumerable<T> |
CreateIterator() |
GetEnumerator() |
Iterator() |
IEnumerator<T> |
First() |
Primeira chamada de MoveNext() |
Next() |
MoveNext() |
IsDone() |
MoveNext() retorna um valor |
CurrentItem() |
Current |
Na tabela acima
temos a
correspondência entre os recursos definidos no padrão Iterator e os usados na
implementação feita na plataforma .NET; com essa implementação podemos
facilmente acessar a maioria das coleções e coleções genéricas definidas na
plataforma .NET.
Diagrama UML
O diagrama UML do padrão Iterator segundo o Gof apresenta os seguintes participantes
1- Iterator - Define uma interface para acessar e
percorrer os elementos de uma coleção (pode ser uma interface/classe
abstrata);
2- ConcreteIterator - Implementa o Iterator e
mantém o controle da posição atual ao atravessar/percorrer o agregado ou
coleção;
3- Aggregate - Define uma interface para criar um
objeto Iterator. (classe abstrata);
4- ConcreteAggregate - Implementa a interface
de criação de Iterator para retornar uma instância do
ConcreteIterator adequado;
Esta é a classe que implementa a expressão. Isso pode ter outras instâncias do
Expression.
5- Client - É uma classe que constrói a árvore de
sintaxe abstrata para um conjunto de instruções na gramática fornecida.
Esta árvore é construída com a ajuda de instâncias das classes
NonTerminalExpression e TerminalExpression.
Essa é a classe que cria a árvore de sintaxe abstrata para um conjunto de
instruções na gramática especificada. Essa árvore é criada com a ajuda de
instâncias das classes NonTerminalExpression e
TerminalExpression.
Quando podemos usar o padrão
Podemos usar o padrão Iterator nos seguintes cenários :
1- Quando sua
coleção tiver uma estrutura de dados complexa e você deseja ocultar sua
complexidade dos clientes;
O padrão encapsula os detalhes do trabalho com uma estrutura de dados
complexa, fornecendo ao cliente vários métodos simples de acesso aos elementos
da coleção.
2- Para reduzir a duplicação do código usada para percorrer coleções no seu
aplicativo;
O código de algoritmos de iteração não triviais tende a ser muito volumoso e
complexo. Mover o código de passagem usado nos iteradores pode ajudar a
tornar o seu código do mais enxuto e limpo.
3- Quando quiser que seu código seja capaz de percorrer diferentes estruturas de
dados ou quando os tipos dessas estruturas forem desconhecidos de antemão.
O padrão fornece algumas interfaces genéricas para coleções e iteradores e
isso permite usar vários tipos de coleções e iteradores que implementam estas
interfaces.
Vantagens do padrão
Como vantagens deste padrão temos que :
- Fornece suporte
a iteração para estruturas de dados que originalmente não a possui;
- Permite definir mais de um tipo de iterador;
- Simplifica o código para acessar diferentes tipos de coleções;
- Trata variáveis de todos os tipos, tamanhos e formas uniformemente, cabendo na
memória ou não
- Segue os princípios SRP e Open/Closed;
Desvantagem
E como desvantagem
podemos citar que :
O padrão é menos eficiente ao percorrer elementos de algumas coleções
especializadas diretamente, usando mais memória do que acesso direto ao
elemento, e este padrão não é recomendado para coleções simples.
Aplicação prática do padrão
Como exemplo de
aplicação do padrão vamos criar uma coleção de objetos
Cliente que é representado pela classe cliente
e contém as propriedades Id e Nome um construtor
padrão :
Nosso objetivo será criar uma coleção de objetos Cliente
e usar o padrão Iterator para iterar sobre os elementos desta coleção:
Para isso vamos implementar o padrão Iterator criando uma interface
Iterator onde vamos definir os métodos First
para retornar o primeiro elemento o método Next
para percorrer a coleção e a propriedade IsDone
para indicar se a iteração sobre a coleção foi concluída ou não:
Vamos criar também uma classe concreta para tratar a coleção com os seguintes métodos :
- Count - usado para contar elementos da coleção;
- Add - usado para incluir elementos na coleção;
- Get - usado para obter um elemento da coleção;
Implementação prática
Levando em conta este cenário vamos implementar o padrão Iterator usando uma aplicação Console .NET Core (.NET 5.0) criada no VS 2019 Community.
A seguir temos o diagrama de classes obtido a partir do VS 2019 na implementação do padrão:
Podemos identificar as seguinte classes :
-
IAbstractIterator - Define a interface que vai
permitir retornar o objeto Iterator;
-
IAbstractCollection - Representa o
Aggregate;
- ConcreteCollection - Implementa a
interface IAbstractCollection para retornar uma
instância da classe Iterator;
- Iterator -
Implementa a interface
AbstractIterator e também mantém o controle da posição atual na travessia
dos elementos;
A seguir temos o código usado na implementação:
1- A interface IAbstractIterator
//Iterator public interface IAbstractIterator { Cliente First(); Cliente Next(); bool IsDone { get; } } |
2- A Interface IAbstractCollection
//Aggregate public interface IAbstractCollection { Iterator CreateIterator(); } |
3- Classe ConcreteCollection
using
System.Collections.Generic;
namespace Iterator2 //Cria o Iterator // Conta os itens //Adiciona itens na coleção //Retorna um item da coleção |
4- Classe Iterator
//ConcreteIterator public class Iterator : IAbstractIterator { private int current = 0; private int step = 1;
private ConcreteCollection collection; |
7- Program
|
A execução do projeto irá apresentar o seguinte resultado:
Temos assim um
exemplo básico de implementação do padrão Iterator.
Conhecer o padrão Iterator pode fazer a diferença em cenários onde temos
coleções que dependem de um algoritmo específico para percorrer os seus
elementos.
Você pode usar
este padrão quando tiver diversas coleções com tipos de iterações diferentes.
Para poder reaproveitar essa lógica e reduzir a quantidade de código duplicado,
podemos encapsulara coleção em um Iterator e usar onde for necessário e se a
lógica de iteração mudar, basta alterar na implementação específica do Iterator.
Pegue o código
do projeto aqui :
Iterator1.zip
"Em Deus está a minha salvação e a minha
glória; a rocha da minha fortaleza, e o meu refúgio estão em Deus."
Salmos 62:7
Referências:
NET - Unit of Work - Padrão Unidade de ...
NET - O padrão de projeto Decorator
NET - Padrão de Projeto Builder
C# - O Padrão Strategy (revisitado)
NET - O padrão de projeto Command
NET - Apresentando o padrão Repository