1. Что такое шаблон Iterator

Шаблон итератор - это поведенческий шаблон проектирования, который предоставляет унифицированный метод обхода элементов агрегатного объекта без раскрытия внутреннего представления этого объекта.

2. Характеристики и преимущества шаблона Iterator

Характеристики и преимущества шаблона итератора следующие:

  • Он может скрывать внутреннюю структуру объекта коллекции, отделяя алгоритм обхода от объекта коллекции.
  • Предоставляет стандартизированный способ обхода различных типов объектов коллекций.
  • Упрощает клиентский код, делая его более четким и кратким.
  • Может предоставлять различные реализации итераторов для адаптации к различным потребностям обхода.

3. Примеры практического применения шаблона Iterator

Шаблон итератора имеет множество практических применений, таких как:

  • Обход результирующего набора запроса к базе данных.
  • Обход файлов и папок в файловой системе.
  • Обход элементов в коллекции.

4. Внедрение шаблона Iterator на Golang

4.1 UML-диаграмма классов

Шаблон итератора на Golang

4.2 Введение в пример

На UML-диаграмме классов выше у нас есть две основные роли: Iterator и Collection.

  • Итератор определяет интерфейс для обхода объекта коллекции, включая метод HasNext() для определения наличия следующего элемента и метод Next() для получения следующего элемента.
  • ConcreteIterator - это конкретный класс реализации Iterator, реализующий методы HasNext() и Next().
  • Коллекция определяет интерфейс для создания объектов итераторов с методом CreateIterator().
  • ConcreteCollection - это конкретный класс реализации Collection, реализующий метод CreateIterator().

4.3 Шаги реализации

Затем мы реализуем шаблон Iterator на Golang поэтапно.

4.3.1 Определение интерфейса Iterator и класса Concrete Iterator

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 Определение интерфейса объекта, который можно перебирать, и конкретного класса объекта, который можно перебирать

type Collection interface {
    CreateIterator() Iterator
}

type ConcreteCollection struct {
    items []interface{}
}

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

4.3.3 Реализация логики генерации итератора в классе объекта, который можно перебирать

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

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

Запуск приведенного выше кода приведет к следующему выводу:

Golang
Python
Java

В приведенном выше коде мы определяем класс ConcreteCollection, реализующий интерфейс Collection, с его методом CreateIterator(), возвращающим объект итератора. Мы используем этот объект итератора в функции main() для обхода.

4.4 Расширение реализации шага: упрощение реализации итератора с использованием генераторной функции

В Golang мы можем упростить реализацию итераторов, используя генераторную функцию (yield). Вот пример использования генераторной функции:

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)
    }
}

В приведенном выше коде мы определяем функцию GenerateItems(), возвращающую только для чтения канал (<-chan), и используем yield для последовательной отправки элементов в этот канал внутри этой функции. В функции main() мы используем range для обхода этого только для чтения канала и вывода значений элементов.

Таким образом, мы упростили реализацию итератора, используя генераторную функцию.