В этой статье я объясню концепцию шаблона проектирования Iterator, цели, плюсы и минусы, сценарии и способы реализации, а также предоставлю два экземпляра и модульные тесты.

Концепция

Шаблон Iterator — это поведенческий шаблон проектирования, который обеспечивает способ обхода элементов в коллекции без раскрытия базовой структуры данных. Он инкапсулирует логику обхода в отдельный объект, называемый итератором. Это позволяет легко переключать стратегии обхода или использовать один и тот же интерфейс итератора для разных коллекций.

Шаблон Iterator включает в себя два основных компонента: Iterator и Aggregate.

  1. Агрегат: коллекция, в которой хранятся элементы.
  2. Итератор: объект, отвечающий за обход элементов в коллекции.

Цели

Цели шаблона Iterator:

  1. Инкапсулируйте логику обхода в отдельный объект итератора.
  2. Обеспечьте единый интерфейс для обхода различных типов коллекций.
  3. Разрешить легкое переключение стратегий обхода.
  4. Отделите клиентский код от базовой структуры данных.

За и против

Преимущества использования шаблона итератора:

  1. Упрощает клиентский код, абстрагируя логику обхода.
  2. Поддерживает различные алгоритмы обхода.
  3. Облегчает добавление новых структур данных без изменения существующего кода.

Недостатки использования шаблона итератора:

  1. Это может привести к дополнительной сложности, если вариант использования не требует нескольких стратегий обхода.
  2. Накладные расходы на производительность из-за создания объектов итераторов.

Сценарии

Вот несколько примеров реальных сценариев, в которых можно применить шаблон Iterator:

1. Обход файловой системы

Когда вам нужно пройти через каталоги и подкаталоги для доступа к файлам, вы можете реализовать итератор, который абстрагирует логику обхода, позволяя пользователям получать доступ к файлам, не зная базовой структуры файловой системы.

2. Результаты поиска

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

3. Результаты запроса к базе данных

При запросе к базе данных вы можете перебирать возвращенный набор результатов различными способами, например, построчно или столбец за столбцом. Шаблон Iterator можно использовать для реализации этих различных методов обхода, сохраняя при этом клиентский код отделенным от кода доступа к базе данных.

4. Древовидные структуры

При работе с древовидными структурами данных, такими как XML-документы, объекты JSON или организационные иерархии, может потребоваться обход узлов дерева различными способами (предварительный порядок, пост-порядок или порядок уровней). Шаблон Iterator можно использовать для реализации этих алгоритмов обхода, сохраняя при этом клиентский код независимым от древовидной структуры.

5. Обход графа

Структуры данных графа имеют разные алгоритмы обхода (например, поиск в глубину, поиск в ширину). Итератор может быть реализован для каждого алгоритма, позволяя пользователям перемещаться по графу с использованием различных стратегий без изменения структуры графа.

Как реализовать

Ниже приведены шаги по реализации шаблона итератора в Go.

  1. Определите интерфейс итератора.
  2. Определите совокупный интерфейс.
  3. Реализовать заполнитель бетона.
  4. Реализуйте конкретный итератор.
  5. Используйте шаблон итератора в клиентском коде.

Первый случай

Сначала я реализую простой пример. Приведенный ниже код является содержимым файла instance.go.

package sample

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

type Aggregate interface {
   Iterator() Iterator
}

// IntegerCollection is used to implement the concrete aggregate. Step 3
type IntegerCollection struct {
   items []int
}

func (c *IntegerCollection) Iterator() Iterator {
   return &IntegerIterator{collection: c, index: -1}
}

func NewIntegerCollection(items []int) *IntegerCollection {
   return &IntegerCollection{items: items}
}

// IntegerIterator is used to implement the concrete iterator. Step 4
type IntegerIterator struct {
   collection *IntegerCollection
   index      int
}

func (it *IntegerIterator) HasNext() bool {
   return it.index < len(it.collection.items)-1
}

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

В приведенном выше коде я реализую все вещи в соответствии с «Как реализовать», о котором я упоминал ранее. Сначала Iterator определяется как интерфейс итератора. Второй шаг, Aggregate, определяется как интерфейс Aggregate. После этого IntegerCollection используется для реализации конкретного агрегата. В конце концов, IntegerIterator используется для реализации конкретного итератора.

Следующий код является содержимым файла instance_test.go.

package sample

import (
   "testing"

   "github.com/go-playground/assert/v2"
)

func TestIntegerIterator(t *testing.T) {
   items := []int{1, 2, 3, 4, 5}
   collection := NewIntegerCollection(items)
   iterator := collection.Iterator()

   expectedItems := []int{1, 2, 3, 4, 5}
   i := 0

   for iterator.HasNext() {
      item := iterator.Next()
      assert.Equal(t, item, expectedItems[i])
      if item != expectedItems[i] {
         t.Errorf("Expected %d, but got %d", expectedItems[i], item)
      }
      i++
   }

   if i != len(expectedItems) {
      t.Errorf("Expected to iterate %d times, but iterated %d times", len(expectedItems), i)
   }
}

func TestEmptyIntegerCollection(t *testing.T) {
   var items []int
   collection := NewIntegerCollection(items)
   iterator := collection.Iterator()

   assert.Equal(t, false, iterator.HasNext())
   if iterator.HasNext() {
      t.Errorf("Expected HasNext() to return false for empty collection")
   }

   assert.Equal(t, nil, iterator.Next())
   if iterator.Next() != nil {
      t.Errorf("Expected Next() to return nil for empty collection")
   }
}

В приведенном выше коде я проверяю элемент результата итератора и пустую ситуацию элементов.

Скриншот результатов теста ниже.

Второй экземпляр

Я реализую Фибоначчи, используя шаблон итератора.

Приведенный ниже код является содержимым файла с именем fabonacci.go.

package fabonacci

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

type Aggregate interface {
   Iterator() Iterator
}

type Fibonacci struct {
   a, b, max int
}

type FibonacciIterator struct {
   f   *Fibonacci
   pos int
}

func (f *Fibonacci) New() Iterator {
   return &FibonacciIterator{f: f, pos: 0}
}

func (fi *FibonacciIterator) HasNext() bool {
   return fi.pos < fi.f.max
}

func (fi *FibonacciIterator) Next() int {
   if fi.pos == 0 {
      fi.pos++
      return fi.f.a
   }

   if fi.pos == 1 {
      fi.pos++
      return fi.f.b
   }

   result := fi.f.a + fi.f.b
   fi.f.a = fi.f.b
   fi.f.b = result
   fi.pos++
   return result
}

В приведенном выше коде структура Фибоначчи содержит первые два числа в последовательности Фибоначчи (a и b) и максимальное количество итераций (max). FibonacciIterator реализует этот интерфейс и поддерживает состояние итерации, отслеживая текущее число в последовательности и обновляя значения a и b по мере необходимости. Метод New возвращает новый итератор для последовательности. HasNext сравнивает pos с max, если pos меньше max, возвращается true. Это означает, что у него есть следующий.

Следующий код является содержимым файла с именем fabonacci_test.go.

package fabonacci

import (
   "testing"

   "github.com/go-playground/assert/v2"
)

func TestFab(t *testing.T) {
   sum := 0
   f := &Fibonacci{a: 0, b: 1, max: 10}
   it := f.New()
   expected := 88
   for it.HasNext() {
      sum += it.Next()
   }

   assert.Equal(t, expected, sum)
}

Скриншот результатов теста выглядит следующим образом.

Заключение

Шаблон Iterator предоставляет ценную абстракцию для последовательного и гибкого обхода коллекций объектов. Благодаря отделению логики обхода от коллекции и включению нескольких алгоритмов обхода этот шаблон продвигает принцип единой ответственности, повторное использование кода и удобство сопровождения. Хотя в определенных ситуациях он может создавать некоторую сложность, его преимущества с точки зрения абстракции и адаптивности делают его мощным инструментом для обработки различных потребностей обхода в разных коллекциях.

Вернитесь к шаблонам поведенческого проектирования и нажмите здесь.

Чтобы просмотреть шаблоны креативного дизайна в Golang, нажмите здесь.

Чтобы просмотреть шаблоны структурного проектирования в Golang, нажмите здесь.

Спасибо, что читаете. Если вам понравилась моя статья, хлопайте в ладоши и подписывайтесь на меня. Я с удовольствием отвечу на все ваши вопросы, если вы спросите меня в комментарии. Нажмите на следующую ссылку, чтобы стать средним участником.

Нажмите, чтобы стать средним участником и читать неограниченное количество историй!