Lapa Knowledge Base
Поведенческие паттерны

Iterator

Паттерн Iterator

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

Проблема

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

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

Решение

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


Структура

Iterator
├── next(): any
├── hasNext(): boolean
└── current(): any

ConcreteIterator
├── collection: ConcreteCollection
├── position: number
├── next(): any
├── hasNext(): boolean
└── current(): any

IterableCollection
├── createIterator(): Iterator
└── getItems(): any[]

ConcreteCollection
├── items: any[]
├── createIterator(): Iterator
└── getItems(): any[]

Пример реализации

JavaScript

// Интерфейс итератора
class Iterator {
    next() {
        throw new Error('Метод next должен быть переопределен');
    }
    
    hasNext() {
        throw new Error('Метод hasNext должен быть переопределен');
    }
    
    current() {
        throw new Error('Метод current должен быть переопределен');
    }
}

// Интерфейс коллекции
class IterableCollection {
    createIterator() {
        throw new Error('Метод createIterator должен быть переопределен');
    }
}

// Конкретная коллекция - список задач
class TaskList extends IterableCollection {
    constructor() {
        super();
        this.tasks = [];
    }
    
    addTask(task) {
        this.tasks.push(task);
    }
    
    getTasks() {
        return this.tasks;
    }
    
    createIterator() {
        return new TaskListIterator(this);
    }
}

// Конкретный итератор для списка задач
class TaskListIterator extends Iterator {
    constructor(taskList) {
        super();
        this.taskList = taskList;
        this.position = 0;
    }
    
    next() {
        if (this.hasNext()) {
            const task = this.taskList.getTasks()[this.position];
            this.position++;
            return task;
        }
        return null;
    }
    
    hasNext() {
        return this.position < this.taskList.getTasks().length;
    }
    
    current() {
        if (this.position > 0 && this.position <= this.taskList.getTasks().length) {
            return this.taskList.getTasks()[this.position - 1];
        }
        return null;
    }
    
    reset() {
        this.position = 0;
    }
}

// Итератор для обхода в обратном порядке
class ReverseTaskListIterator extends Iterator {
    constructor(taskList) {
        super();
        this.taskList = taskList;
        this.position = taskList.getTasks().length - 1;
    }
    
    next() {
        if (this.hasNext()) {
            const task = this.taskList.getTasks()[this.position];
            this.position--;
            return task;
        }
        return null;
    }
    
    hasNext() {
        return this.position >= 0;
    }
    
    current() {
        if (this.position >= 0 && this.position < this.taskList.getTasks().length) {
            return this.taskList.getTasks()[this.position + 1];
        }
        return null;
    }
    
    reset() {
        this.position = this.taskList.getTasks().length - 1;
    }
}

// Итератор для фильтрации задач по статусу
class FilteredTaskIterator extends Iterator {
    constructor(taskList, status) {
        super();
        this.taskList = taskList;
        this.status = status;
        this.position = 0;
        this.filteredTasks = this.taskList.getTasks().filter(task => task.status === status);
    }
    
    next() {
        if (this.hasNext()) {
            const task = this.filteredTasks[this.position];
            this.position++;
            return task;
        }
        return null;
    }
    
    hasNext() {
        return this.position < this.filteredTasks.length;
    }
    
    current() {
        if (this.position > 0 && this.position <= this.filteredTasks.length) {
            return this.filteredTasks[this.position - 1];
        }
        return null;
    }
}

// Использование
const taskList = new TaskList();

// Добавляем задачи
taskList.addTask({ id: 1, title: 'Купить молоко', status: 'pending' });
taskList.addTask({ id: 2, title: 'Прочитать книгу', status: 'completed' });
taskList.addTask({ id: 3, title: 'Написать код', status: 'in_progress' });
taskList.addTask({ id: 4, title: 'Погулять с собакой', status: 'pending' });
taskList.addTask({ id: 5, title: 'Приготовить ужин', status: 'completed' });

console.log('=== Обход задач в прямом порядке ===');
const iterator = taskList.createIterator();
while (iterator.hasNext()) {
    const task = iterator.next();
    console.log(`${task.id}: ${task.title} (${task.status})`);
}

console.log('\n=== Обход задач в обратном порядке ===');
const reverseIterator = new ReverseTaskListIterator(taskList);
while (reverseIterator.hasNext()) {
    const task = reverseIterator.next();
    console.log(`${task.id}: ${task.title} (${task.status})`);
}

console.log('\n=== Обход только завершенных задач ===');
const completedIterator = new FilteredTaskIterator(taskList, 'completed');
while (completedIterator.hasNext()) {
    const task = completedIterator.next();
    console.log(`${task.id}: ${task.title} (${task.status})`);
}

TypeScript с интерфейсами

// Интерфейс итератора
interface Iterator<T> {
    next(): T | null;
    hasNext(): boolean;
    current(): T | null;
}

// Интерфейс коллекции
interface IterableCollection<T> {
    createIterator(): Iterator<T>;
}

// Конкретная коллекция - дерево файлов
class FileNode {
    constructor(
        public name: string,
        public isDirectory: boolean = false,
        public children: FileNode[] = []
    ) {}
    
    addChild(child: FileNode): void {
        if (this.isDirectory) {
            this.children.push(child);
        }
    }
}

class FileTree implements IterableCollection<FileNode> {
    constructor(private root: FileNode) {}
    
    createIterator(): Iterator<FileNode> {
        return new FileTreeIterator(this.root);
    }
}

// Итератор для обхода дерева файлов (DFS)
class FileTreeIterator implements Iterator<FileNode> {
    private stack: FileNode[] = [];
    
    constructor(private root: FileNode) {
        this.stack.push(root);
    }
    
    next(): FileNode | null {
        if (!this.hasNext()) {
            return null;
        }
        
        const node = this.stack.pop()!;
        
        // Добавляем дочерние узлы в обратном порядке для правильного обхода
        if (node.isDirectory) {
            for (let i = node.children.length - 1; i >= 0; i--) {
                this.stack.push(node.children[i]);
            }
        }
        
        return node;
    }
    
    hasNext(): boolean {
        return this.stack.length > 0;
    }
    
    current(): FileNode | null {
        return this.stack.length > 0 ? this.stack[this.stack.length - 1] : null;
    }
}

// Итератор для обхода только файлов (не директорий)
class FileOnlyIterator implements Iterator<FileNode> {
    private fileIterator: FileTreeIterator;
    
    constructor(root: FileNode) {
        this.fileIterator = new FileTreeIterator(root);
    }
    
    next(): FileNode | null {
        while (this.fileIterator.hasNext()) {
            const node = this.fileIterator.next();
            if (node && !node.isDirectory) {
                return node;
            }
        }
        return null;
    }
    
    hasNext(): boolean {
        // Проверяем, есть ли еще файлы
        const current = this.fileIterator.current();
        return this.fileIterator.hasNext() && 
               (current === null || !current.isDirectory || this.hasFilesAhead());
    }
    
    private hasFilesAhead(): boolean {
        const tempIterator = new FileTreeIterator(this.fileIterator['root']);
        while (tempIterator.hasNext()) {
            const node = tempIterator.next();
            if (node && !node.isDirectory) {
                return true;
            }
        }
        return false;
    }
    
    current(): FileNode | null {
        return this.fileIterator.current();
    }
}

// Использование
const root = new FileNode('root', true);
const documents = new FileNode('documents', true);
const images = new FileNode('images', true);
const downloads = new FileNode('downloads', true);

root.addChild(documents);
root.addChild(images);
root.addChild(downloads);

documents.addChild(new FileNode('report.pdf', false));
documents.addChild(new FileNode('presentation.pptx', false));
images.addChild(new FileNode('photo1.jpg', false));
images.addChild(new FileNode('photo2.png', false));
downloads.addChild(new FileNode('software.zip', false));

const fileTree = new FileTree(root);

console.log('=== Обход всех узлов дерева ===');
const treeIterator = fileTree.createIterator();
while (treeIterator.hasNext()) {
    const node = treeIterator.next();
    console.log(`${node.isDirectory ? '[DIR]' : '[FILE]'} ${node.name}`);
}

console.log('\n=== Обход только файлов ===');
const fileIterator = new FileOnlyIterator(root);
while (fileIterator.hasNext()) {
    const file = fileIterator.next();
    console.log(`[FILE] ${file!.name}`);
}

Когда использовать

Используйте Iterator, когда:

  • Нужно обходить сложные структуры данных
  • Хотите скрыть детали реализации коллекции
  • Нужны разные способы обхода одной коллекции
  • Хотите предоставить единый интерфейс для обхода разных коллекций

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

  • Единый интерфейс для обхода разных коллекций
  • Поддержка различных способов обхода
  • Принцип единственной ответственности
  • Принцип открытости/закрытости: можно добавлять новые итераторы

Недостатки

  • Может быть избыточным для простых коллекций
  • Увеличивает количество классов
  • Может снизить производительность

Отличия от других паттернов

  • Composite — группирует объекты в древовидные структуры
  • Visitor — выполняет операции над элементами коллекции
  • Chain of Responsibility — передает запросы по цепочке

Реальный пример

// Iterator для обхода результатов поиска
class SearchResultIterator {
    constructor(results) {
        this.results = results;
        this.position = 0;
        this.pageSize = 10;
    }
    
    next() {
        if (this.hasNext()) {
            const result = this.results[this.position];
            this.position++;
            return result;
        }
        return null;
    }
    
    hasNext() {
        return this.position < this.results.length;
    }
    
    current() {
        return this.position > 0 ? this.results[this.position - 1] : null;
    }
    
    // Дополнительные методы для пагинации
    getCurrentPage() {
        return Math.floor(this.position / this.pageSize) + 1;
    }
    
    getTotalPages() {
        return Math.ceil(this.results.length / this.pageSize);
    }
    
    goToPage(page) {
        this.position = (page - 1) * this.pageSize;
    }
}

// Использование
const searchResults = Array.from({ length: 25 }, (_, i) => ({
    id: i + 1,
    title: `Результат ${i + 1}`,
    url: `https://example.com/result-${i + 1}`
}));

const iterator = new SearchResultIterator(searchResults);

console.log('=== Обход результатов поиска ===');
console.log(`Всего результатов: ${searchResults.length}`);
console.log(`Всего страниц: ${iterator.getTotalPages()}`);

// Обходим первую страницу
console.log('\n--- Страница 1 ---');
let count = 0;
while (iterator.hasNext() && count < 10) {
    const result = iterator.next();
    console.log(`${result.id}: ${result.title}`);
    count++;
}

console.log(`\nТекущая страница: ${iterator.getCurrentPage()}`);

Iterator — это универсальный способ обхода коллекций!

Copyright © 2026