Iterator (Итератор)

Паттерн Iterator (Итератор) предоставляет способ последовательного доступа к элементам коллекции без раскрытия её внутренней структуры. Он выносит логику обхода из самой коллекции, обеспечивая единый интерфейс для перебора различных структур данных.

Когда применять Iterator

  • Когда нужно пройти по элементам коллекции, не раскрывая её внутреннего устройства.
  • Когда требуется иметь несколько независимых итераторов для одной и той же коллекции.
  • Когда необходимо реализовать собственные способы обхода элементов (например, в обратном порядке).

PlantUML-диаграмма

UML диаграмма Iterator

// Интерфейс итератора
public interface IIterator<T>
{
    bool HasNext();
    T Next();
}

// Интерфейс коллекции
public interface IAggregate<T>
{
    IIterator<T> CreateIterator();
}

// Конкретная коллекция
public class WordsCollection : IAggregate<string>
{
    private List<string> _items = new List<string>();

    public void AddItem(string item) => _items.Add(item);

    public IIterator<string> CreateIterator()
    {
        return new AlphabeticalIterator(this);
    }

    public int Count => _items.Count;
    public string this[int index] => _items[index];
}

// Конкретный итератор
public class AlphabeticalIterator : IIterator<string>
{
    private WordsCollection _collection;
    private int _position = -1;

    public AlphabeticalIterator(WordsCollection collection)
    {
        _collection = collection;
    }

    public bool HasNext()
    {
        return _position < _collection.Count - 1;
    }

    public string Next()
    {
        _position++;
        return _collection[_position];
    }
}

// Клиентский код
class Program
{
    static void Main()
    {
        var collection = new WordsCollection();
        collection.AddItem("Банан");
        collection.AddItem("Яблоко");
        collection.AddItem("Апельсин");

        var iterator = collection.CreateIterator();

        Console.WriteLine("Перебор элементов коллекции:");
        while (iterator.HasNext())
        {
            Console.WriteLine(iterator.Next());
        }
    }
}
// В Go для перебора хватает range, но паттерн виден,
// когда порядок обхода задаёт сама коллекция.
type Iterator[T any] interface {
	HasNext() bool
	Next() T
}

type Aggregate[T any] interface {
	CreateIterator() Iterator[T]
}

type WordsCollection struct {
	items []string
}

func (c *WordsCollection) AddItem(item string) {
	c.items = append(c.items, item)
}

func (c *WordsCollection) CreateIterator() Iterator[string] {
	return &AlphabeticalIterator{collection: c, position: -1}
}

type AlphabeticalIterator struct {
	collection *WordsCollection
	position   int
}

func (i *AlphabeticalIterator) HasNext() bool {
	return i.position < len(i.collection.items)-1
}

func (i *AlphabeticalIterator) Next() string {
	i.position++
	return i.collection.items[i.position]
}

func main() {
	collection := &WordsCollection{}
	collection.AddItem("Банан")
	collection.AddItem("Яблоко")
	collection.AddItem("Апельсин")

	iterator := collection.CreateIterator()

	fmt.Println("Перебор элементов коллекции:")
	for iterator.HasNext() {
		fmt.Println(iterator.Next())
	}
}
from typing import Iterator, Protocol


class Aggregate(Protocol):
    def create_iterator(self) -> Iterator[str]: ...


class WordsCollection:
    def __init__(self) -> None:
        self._items: list[str] = []

    def add_item(self, item: str) -> None:
        self._items.append(item)

    # Итератор в Python — часть языка: генератор уже реализует протокол,
    # отдельный класс с has_next/next писать не нужно.
    def create_iterator(self) -> Iterator[str]:
        yield from self._items


collection = WordsCollection()
collection.add_item("Банан")
collection.add_item("Яблоко")
collection.add_item("Апельсин")

print("Перебор элементов коллекции:")
for word in collection.create_iterator():
    print(word)
class WordsCollection {
    private readonly items: string[] = [];

    addItem(item: string): void {
        this.items.push(item);
    }

    // Протокол итератора встроен в язык: достаточно генератора,
    // и коллекция сразу работает в for...of.
    *[Symbol.iterator](): Iterator<string> {
        yield* this.items;
    }
}

const collection = new WordsCollection();
collection.addItem("Банан");
collection.addItem("Яблоко");
collection.addItem("Апельсин");

console.log("Перебор элементов коллекции:");
for (const word of collection) {
    console.log(word);
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

// Iterable/Iterator есть в стандартной библиотеке — свой интерфейс не нужен.
class WordsCollection implements Iterable<String> {
    private final List<String> items = new ArrayList<>();

    void addItem(String item) {
        items.add(item);
    }

    @Override
    public Iterator<String> iterator() {
        return new Iterator<>() {
            private int position = 0;

            public boolean hasNext() {
                return position < items.size();
            }

            public String next() {
                return items.get(position++);
            }
        };
    }
}

public class Program {
    public static void main(String[] args) {
        WordsCollection collection = new WordsCollection();
        collection.addItem("Банан");
        collection.addItem("Яблоко");
        collection.addItem("Апельсин");

        System.out.println("Перебор элементов коллекции:");
        for (String word : collection) {
            System.out.println(word);
        }
    }
}
class WordsCollection : Iterable<String> {
    private val items = mutableListOf<String>()

    fun addItem(item: String) {
        items += item
    }

    // Iterable — часть стандартной библиотеки, свой интерфейс не нужен.
    override fun iterator(): Iterator<String> = items.iterator()
}

fun main() {
    val collection = WordsCollection().apply {
        addItem("Банан")
        addItem("Яблоко")
        addItem("Апельсин")
    }

    println("Перебор элементов коллекции:")
    collection.forEach { println(it) }
}

Плюсы и минусы

Плюсы Минусы
Упрощает обход коллекций без знания их структуры Увеличивает количество классов
Позволяет реализовать разные способы обхода Может быть избыточен для простых коллекций
Следует принципу единственной ответственности Итератор может нарушить инкапсуляцию коллекции

Заключение

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