Padrão de projeto - Iterator
Tags: braziliandevs, python
Published: 2022-07-11 04:06:40

O **Iterator** é um padrão de projeto comportamental que permite percorrer em elementos de uma coleção sem precisar saber como isso está acontecendo debaixo dos panos, independente de ser lista, pilha, árvore, etc.

---

## Estrutura de dados

Dentre as estruturas citadas acima, vamos utilizar uma árvore como exemplo:

![Árvores com setas indicando o tipo de acesso, em profundidade e em largura](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2h4ytm4xvgml0gqdcxix.png)

Como podemos ver na imagem acima, a árvore pode ser acessada de diferentes maneiras, de acordo com o solicitado pelo cliente, podendo ter acesso em **profundidade** ou em **largura**, além da possibilidade de implementação de outros tipos de acesso aos elementos.

O Iterator é um padrão que extrai essas diferentes formas de percorrer uma árvore para um objeto chamado **iterável**, segue a imagem:

![Arvores e UML contendo os métodos de busca em profundidade e em largura](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tt3zm1c3fm6tcniear5r.png)

---

_Exemplo simples:
Implementar um mecanismo de busca, onde o cliente vai utilizar o método getBusca() sem saber com qual algoritmo o método foi desenvolvido - Bubble Sort, Merge Sort, Quick Sort..._

---
## Quando usar?

Esse padrão pode ser utilizado quando a complexidade da estrutura de dados não é interessante para o cliente. O acesso aos elementos é fornecido através de métodos simples, encapsulando os detalhes do algoritmo, trazendo mais proteção às coleções, as quais não serão manipuladas diretamente.

Outra vantagem de definir a **responsabilidade** de percorrer a estrutura de dados para o iterador, tirando ela de dentro da regra de negócio, se dá no tempo que os algoritmos não triviais levam para buscar um elemento. Também pelo fato de que o objeto iterador tem seu próprio estado de iteração, sendo possível mais de um objeto percorrer a mesma coleção em paralelo. Assim o código do cliente se torna mais limpo e focado em seus objetivos.

---
## Iterator no Python

O python possui um módulo Iterator e Iterable, para criar os iteradores e os objetos iteráveis.

```python
from collections.abc import Iterator, Iterable
```

Este módulo implementa os métodos \_\_iter()\_\_ e \_\_next()\_\_, os quais são necessários para um objeto ser iterável no Python.

```python
import random

class RandomIterable:

def __iter__(self):
    return self
def __next__(self):
    if random.choice(["go", "go", "stop"]) == "stop":
        raise StopIteration
        # Sinaliza o fim da iteração
    return 1
```



### Criando o iterator.py

Em um arquivo chamado iterator.py, criamos a classe InsertOrder, a qual vai definir a ordem que os elementos de uma coleção serão percorridos - implementamos a função \_\_next()\_\_ aqui.

```python
class InsertOrder(Iterator):
_position: int = None
_reverse: bool = False

def __init__(self, collection: Iterable,
            reverse: bool = False) -> None:
    self._collection = collection
    self._reverse = reverse
    self._position = -1 if reverse else 0

def __next__(self):
    try:
        value = self._collection[self._position]
        self._position += -1 if self._reverse else 1
    except IndexError:
        raise StopIteration()
    return value
```

* _classe InsertOrder: possui atributos de posição do elemento e indicação se a coleção deve ser iterada na ordem normal ou reversa_
* _função \_\_init\_\_: construtor da classe_
* _função \_\_next\_\_: itera o objeto até não ter mais posições de índices, caindo na except, onde finaliza a iteração, aqui também verificamos se a busca vai ser normal ou reversa_

No mesmo arquivo, criamos a classe WordsCollection, essa definirá um objeto iteravel, onde será implementada a segunda função necessária do Iterator, a \_\_iter()\_\_.

```python
class WordsCollection(Iterable):
    def __init__(self, collection: list = []) -> None:
        self._collection = collection

    def __iter__(self) -> InsertOrder:
        return InsertOrder(self._collection)

    def get_reverse(self) -> InsertOrder:
        return InsertOrder(self._collection, True)
    
    def outras_formas(self) -> InsertOrder:
        # desenvolver outras formas de percorrer a coleção
        ...
        return InsertOrder(self.colletion)

    def add_item(self, item):
        self._collection.append(item)
```

* _classe WordsCollection: recebe a estrutura de dados, aqui usamos uma lista para facilitar o entendimento_
* _função \_\_init\_\_: construtor da classe, ele vai receber a coleção para iterar_
* _função \_\_iter\_\_: invoca a classe InsertOrder, passa a coleção como parâmetro e retorna um iterador_
* _função get_reverse: invoca a classe InsertOrder, passa a coleção e a sinalização de ordem reversa como parâmetros e retorna um iterador_
* _função outras_formas: um exemplo criado sem implementação, podendo criar diferentes regras de busca na mesma coleção, retornando também um iterator_
* _função add_item: adiciona elementos à uma coleção_

### Criando o cliente.py

O cliente é quem vai utilizar o iterador, sem precisar saber sobre suas regras de busca, apenas passando a coleção e indicando o método.

```python
def instance_collection(elements: list[str]) -> WordsCollection:
    collection = WordsCollection()
    for element in elements:
        collection.add_item(element)

    return collection
```

* _função instance_collection: instancia um **objeto iterável**, nesse caso uma lista de strings e retorna essa coleção_

### Executando o cliente.py

```python
if __name__ == '__main__':
    elements = [
        'IFSC',
        'Programação',
        'Laboratório',
        'Professor',
        'Aluno'
        ]

    collection = instance_collection(elements)
```
* _variável elements: definimos uma lista de elementos_
* _variável collection: chama a função para instanciar um objeto iterável, passando como parametro **elements**_

Iterando pela **collection** de forma normal:
```python
for e in collection.__iter__():
    print(e)
```
Saída:
```cmd
IFSC
Programação
Laboratório
Professor
Aluno
```

Iterando pela **collection** de forma reversa:
```python
for e in collection.get_reverse():
    print(e)
```
Saída:
```cmd
Aluno
Professor
Laboratório
Programação
IFSC
```

##  Considerações

Com algumas adaptações no **iterator.py**, podemos fazer a iteração de diversas coleções e estruturas, sem a necessidade de alterar o código **cliente.py**, pois esse apenas consumirá o iterador.

---

Conhecendo mais sobre o módulo [collections.abc](https://docs.python.org/pt-br/3/library/collections.abc.html) e [iterator](https://wiki.python.org/moin/Iterator) do Python.

Pessoas que compartilham conhecimentos na comunidade: [vcwild](https://www.twitch.tv/videos/1500280222?collection=LjAXvKRbmBY8zw)

[Principal referência](https://refactoring.guru/pt-br/design-patterns/iterator) para desenvolvimento deste artigo.

[Repositório](https://github.com/bugelseif/IteratorPattern) com o código completo.

**Enjoy!**