Что такое паттерн проектирования «Итератор» (Iterator)?

Ответ

Паттерн «Итератор» — это поведенческий паттерн, который предоставляет способ последовательного доступа к элементам составного объекта (коллекции, дерева, графа), не раскрывая его внутреннего представления.

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

Ключевые компоненты:

  1. IIterator<T> — интерфейс итератора с методами MoveNext(), Current и, возможно, Reset().
  2. IAggregate<T> / IEnumerable<T> — интерфейс агрегата (коллекции), который может создавать итератор (метод GetEnumerator()).
  3. Конкретный итератор (Concrete Iterator) — реализует логику обхода конкретной коллекции.
  4. Конкретная коллекция (Concrete Aggregate) — возвращает экземпляр конкретного итератора.

Пример реализации на C#:

// Пользовательский итератор для обхода коллекции в обратном порядке
public class ReverseIterator<T> : IIterator<T>
{
    private readonly List<T> _collection;
    private int _currentPosition;
    public ReverseIterator(List<T> collection)
    {
        _collection = collection;
        _currentPosition = collection.Count - 1;
    }
    public bool MoveNext() => _currentPosition >= 0;
    public T Current => _collection[_currentPosition--];
}

// Пользовательская коллекция
public class MyCollection<T> : IAggregate<T>
{
    private List<T> _items = new List<T>();
    public void Add(T item) => _items.Add(item);
    // Стандартный итератор (foreach)
    public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();
    // Специальный итератор (обратный порядок)
    public IIterator<T> GetReverseIterator() => new ReverseIterator<T>(_items);
}

// Использование
var collection = new MyCollection<int> { 1, 2, 3, 4, 5 };
Console.WriteLine("Standard iteration:");
foreach (var item in collection) Console.Write(item + " "); // 1 2 3 4 5
Console.WriteLine("nReverse iteration:");
var reverseIterator = collection.GetReverseIterator();
while (reverseIterator.MoveNext())
{
    Console.Write(reverseIterator.Current + " "); // 5 4 3 2 1
}

Встроенная поддержка в .NET: Паттерн глубоко интегрирован в язык C# через интерфейсы IEnumerable<T> и IEnumerator<T>. Ключевое слово foreach — это синтаксический сахар для работы с итератором.

// Внутренняя работа foreach
IEnumerator<int> enumerator = collection.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        int item = enumerator.Current;
        // ... работа с item
    }
}
finally
{
    enumerator.Dispose();
}

Преимущества:

  • Единый интерфейс обхода: Клиентский код работает с любыми коллекциями через единый интерфейс итератора.
  • Принцип единой ответственности: Коллекция отвечает за хранение данных, итератор — за логику обхода.
  • Параллельный обход: Можно создать несколько независимых итераторов для одной коллекции.
  • Отложенное выполнение (yield return): В C# с помощью yield return можно создавать итераторы с ленивым выполнением, что эффективно для работы с большими или генерируемыми на лету данными.

Ответ 18+ 🔞

Да ты посмотри, какой хитрый паттерн придумали, блядь! Итератор, сука! Ну ты представляешь, сидит какой-то умник и думает: "А как бы так сделать, чтобы по коллекции похавать, но внутрь её не заглядывать, как в чужую холодильник?"

Вот в чём соль, понимаешь? У тебя есть какая-нибудь куча всего — список, дерево, граф, там, чёрт знает что. И тебе надо по ней пройтись. А если ты будешь в коде лазить и смотреть, как она там устроена внутри — это ж пиздец, чувак. Завяжешься на конкретную структуру намертво. Добавят завтра в коллекцию ещё один костыль — и тебе весь свой код переписывать, ебать его в сраку.

А тут придумали гениальную хуйню: отделить обход от самой коллекции. Коллекция пусть себе хранит данные как хочет, а для обхода будет отдельная сущность — итератор. Он как проводник по зоопарку: ты ему — "давай дальше", а он тебе — "вот следующий зверь, смотри, не подходи близко".

Из чего это говно состоит, блядь:

  1. Интерфейс итератора (IIterator<T>). Это как джойстик, у которого всего три кнопки: MoveNext() (есть ли следующий?), Current (дай текущий!) и иногда Reset() (отмотай нахуй назад, на старт).
  2. Интерфейс коллекции (IAggregate<T>). Это такая штука, которая может породить себе итератор. Говоришь ей "Дай проводника!" — она тебе нового, свежего.
  3. Конкретный итератор. Вот тут уже начинается магия. Он знает, как именно лазить по конкретной коллекции. Вперёд, назад, через один, только чётные — да как угодно, блядь!
  4. Конкретная коллекция. Ну это твой List, Dictionary или своя собственная MySuperCollection. Её задача — когда её попросят итератор, она должна его создать и отдать.

Смотри, как это выглядит в коде, на примере итератора, который идёт задом наперёд:

// Итератор, который начинает с конца и идёт к началу. Ёбнутый, но работает.
public class ReverseIterator<T> : IIterator<T>
{
    private readonly List<T> _collection;
    private int _currentPosition; // Текущая позиция, с которой тыкаем

    public ReverseIterator(List<T> collection)
    {
        _collection = collection;
        // Стартуем не с нуля, а с последнего элемента, гений!
        _currentPosition = collection.Count - 1;
    }

    // Двигаемся? Да, пока не упрёмся в начало (пока позиция >= 0).
    public bool MoveNext() => _currentPosition >= 0;

    // Дай текущий элемент и отступи на шаг назад.
    public T Current => _collection[_currentPosition--];
}

// А это наша коллекция. Моглала бы быть и стандартной, но мы тут свою пишем.
public class MyCollection<T> : IAggregate<T>
{
    private List<T> _items = new List<T>();

    public void Add(T item) => _items.Add(item);

    // Стандартный итератор для foreach
    public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();

    // А это наш кастомный, обратный итератор. Вот он, профит!
    public IIterator<T> GetReverseIterator() => new ReverseIterator<T>(_items);
}

// Использование на практике
var collection = new MyCollection<int>();
collection.Add(1); collection.Add(2); collection.Add(3); collection.Add(4); collection.Add(5);

Console.WriteLine("Обычный обход (foreach):");
foreach (var item in collection) Console.Write(item + " "); // 1 2 3 4 5

Console.WriteLine("nА теперь задом наперёд, как краб:");
var reverseIterator = collection.GetReverseIterator();
while (reverseIterator.MoveNext())
{
    Console.Write(reverseIterator.Current + " "); // 5 4 3 2 1
}

А самое приколючее, что в C# это уже встроено в язык, ёпта! Интерфейсы IEnumerable<T> и IEnumerator<T> — это и есть каноничная реализация паттерна. А волшебное слово foreach — это просто синтаксический сахар, который прячет под капотом вот эту простыню:

// Вот что на самом деле делает foreach. Не благодари.
IEnumerator<int> enumerator = collection.GetEnumerator();
try
{
    while (enumerator.MoveNext()) // "Ну что, есть следующий?"
    {
        int item = enumerator.Current; // "Так давай его сюда!"
        // ... делаем что-то с item
    }
}
finally
{
    enumerator.Dispose(); // Прибраться за собой — дело святое.
}

И зачем это всё, спросишь? А вот зачем:

  • Универсальный доступ. Клиенту похуй, как коллекция устроена внутри. Ему главное — получить итератор и тыкать в кнопки MoveNext и Current.
  • Разделение обязанностей. Коллекция — склад. Итератор — экспедитор. Каждый занимается своим делом, не лезет в чужой монастырь.
  • Несколько обходов сразу. Можно создать два, три, десять итераторов для одной коллекции, и они не будут мешать друг другу. Каждый будет ползать по своим делам.
  • Ленивые вычисления (yield return). Это вообще песня, блядь! Ты можешь генерировать элементы на лету, по мере необходимости, а не грузить в память овердохуищу данных сразу. Экономия — наше всё.

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