1. O que é o Padrão de Iterador

O padrão de iterador é um padrão de design comportamental que fornece um método uniforme para percorrer os elementos de um objeto de agregação sem expor a representação interna do objeto de agregação.

2. Características e Vantagens do Padrão de Iterador

As características e vantagens do padrão de iterador são as seguintes:

  • Pode ocultar a estrutura interna do objeto de coleção, desacoplando o algoritmo de travessia do objeto de coleção.
  • Fornece uma maneira padronizada de percorrer diferentes tipos de objetos de coleção.
  • Simplifica o código do cliente, tornando-o mais claro e conciso.
  • Pode fornecer diferentes implementações de iteradores para se adaptar a diferentes necessidades de travessia.

3. Exemplos de Aplicações Práticas do Padrão de Iterador

O padrão de iterador tem muitas aplicações práticas, como:

  • Percorrer um conjunto de resultados de consulta de banco de dados.
  • Percorrer arquivos e pastas em um sistema de arquivos.
  • Percorrer elementos em uma coleção.

4. Implementação do Padrão de Iterador em Golang

4.1 Diagrama de Classe UML

Padrão de Iterador em Golang

4.2 Introdução do Exemplo

No diagrama de classe UML acima, temos dois papéis principais: Iterador e Coleção.

  • O Iterador define a interface para percorrer um objeto de coleção, incluindo o método HasNext() para determinar se há outro elemento e o método Next() para obter o próximo elemento.
  • O ConcreteIterator é a classe de implementação específica do Iterador, implementando os métodos HasNext() e Next().
  • A Coleção define a interface para criar objetos iteradores, com o método CreateIterator().
  • O ConcreteCollection é a classe de implementação específica da Coleção, implementando o método CreateIterator().

4.3 Etapas de Implementação

A seguir, implementaremos o padrão de iterador em Golang passo a passo.

4.3.1 Definir a Interface do Iterador e a Classe Concreta do Iterador

type Iterator interface {
    HasNext() bool
    Next() interface{}
}

type ConcreteIterator struct {
    collection *ConcreteCollection
    index      int
}

func (it *ConcreteIterator) HasNext() bool {
    if it.index < len(it.collection.items) {
        return true
    }
    return false
}

func (it *ConcreteIterator) Next() interface{} {
    if it.HasNext() {
        item := it.collection.items[it.index]
        it.index++
        return item
    }
    return nil
}

4.3.2 Definir a Interface do Objeto Iterável e a Classe Concreta do Objeto Iterável

type Collection interface {
    CreateIterator() Iterator
}

type ConcreteCollection struct {
    items []interface{}
}

func (c *ConcreteCollection) CreateIterator() Iterator {
    return &ConcreteIterator{
        collection: c,
        index:      0,
    }
}

4.3.3 Implementar a Lógica de Geração do Iterador na Classe do Objeto Iterável

func main() {
    collection := &ConcreteCollection{
        items: []interface{}{"Golang", "Python", "Java"},
    }

    iterator := collection.CreateIterator()
    for iterator.HasNext() {
        item := iterator.Next()
        fmt.Println(item)
    }
}

A execução do código acima produzirá a seguinte saída:

Golang
Python
Java

No código acima, definimos uma classe ConcreteCollection que implementa a interface Collection, com seu método CreateIterator() retornando um objeto iterador. Usamos este objeto iterador na função main() para travessia.

4.4 Extensão da Etapa de Implementação: Simplificando a Implementação do Iterador Usando a Função Geradora

Em Golang, podemos simplificar a implementação de iteradores usando uma função geradora (yield). Aqui está um exemplo de uso de uma função geradora:

func GenerateItems() <-chan interface{} {
    items := []interface{}{"Golang", "Python", "Java"}

    out := make(chan interface{})
    go func() {
        defer close(out)
        for _, item := range items {
            out <- item
        }
    }()
    return out
}

func main() {
    for item := range GenerateItems() {
        fmt.Println(item)
    }
}

No código acima, definimos uma função GenerateItems() que retorna um canal somente leitura (<-chan), e usamos yield para enviar sequencialmente elementos para o canal dentro desta função. Na função main(), usamos range para percorrer este canal somente leitura e exibir os valores dos elementos.

Dessa forma, simplificamos a implementação do iterador usando uma função geradora.