Перейти к содержанию

Итератор (Iterator)

Iterator позволяет обходить коллекцию, не раскрывая клиенту ее внутреннее устройство.

Опора на ООП

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

Что показывает пример на 1С

  • DataProcessors.WordsCollection скрывает внутренний Array и отдает обходчик через Iterator(Reverse = False).
  • DataProcessors.AlphabeticalOrderIterator хранит Collection, Position, Reverse и предоставляет методы Next() и Value().
  • Клиент работает с обходом как с отдельным объектом и не зависит от способа хранения коллекции.

Пример

WordsCollection = DataProcessors.WordsCollection.Create();
WordsCollection.Add("Gamma");
WordsCollection.Add("Alpha");
WordsCollection.Add("Beta");

Iterator = WordsCollection.Iterator();

While Iterator.Next() Do
    Message(Iterator.Value());
EndDo;
Var Collection;

#Region Public

Procedure Add(Value) Export

    Collection.Add(Value);

EndProcedure

Function Iterator(Reverse = False) Export

    Iterator = DataProcessors.AlphabeticalOrderIterator.Create();
    Iterator.Init(Collection, Reverse);
    Return Iterator;

EndFunction

#EndRegion

Collection = New Array;
Var Collection;
Var Reverse;
Var Position;

#Region Public

Procedure Init(Collection_, Reverse_) Export

    Collection = Collection_;
    Reverse = Reverse_;
    Position = ?(Reverse, -1, 0);

EndProcedure

Function Next() Export

    Try
        Position = Position + ?(Reverse, -1, 1);
    Except
        Return False;
    EndTry;

    Return True;

EndFunction

Function Value() Export

    Return Collection[Position];

EndFunction

#EndRegion

Вариации

У Iterator есть близкая вариация, которую часто называют генератором.

Классический итератор

В классическом варианте коллекция уже существует целиком, а итератор только знает, как по ней пройти.

  • данные уже лежат в Array, ValueTable, выборке или другой структуре;
  • итератор хранит позицию обхода;
  • следующий элемент берется из уже подготовленного источника.

Именно такой вариант показан в примере выше: WordsCollection хранит массив, а AlphabeticalOrderIterator только управляет позицией и направлением обхода.

Генератор

Генератор похож на итератор по внешнему контракту, но отличается источником данных.

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

Если упростить:

  • Iterator отвечает на вопрос: как пройти уже существующую коллекцию?
  • Generator отвечает на вопрос: как выдавать следующий элемент по требованию?

Для 1С это особенно полезно, когда не хочется сначала собирать всю коллекцию в память, а потом отдельно обходить ее.

Например:

  • построчное чтение файла;
  • поэтапная обработка большой выборки;
  • чтение данных из внешнего API страницами;
  • последовательное построение команд, задач или событий.

В 1С нет встроенного yield, как в Python или C#, поэтому генератор обычно оформляется как обычный объект с состоянием. Снаружи он нередко выглядит почти так же, как итератор:

While Generator.Next() Do
    Process(Generator.Value());
EndDo;

Практический пример для 1С: получение списка через HTTP API с пагинацией.

Представим, что внешний сервис отдает клиентов не целиком, а страницами по 100 записей:

  • первый запрос получает первую страницу и nextPageToken;
  • второй запрос получает следующую страницу по этому токену;
  • и так далее, пока страницы не закончатся.

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

Генератор позволяет оформить это лениво:

  • объект хранит HttpClient, NextPageToken, текущий буфер элементов и признак завершения;
  • при Next() он проверяет, есть ли элемент в текущем буфере;
  • если буфер пуст, делает следующий HTTP-запрос, заполняет буфер новой страницей и только потом отдает очередной элемент;
  • если сервис больше не вернул nextPageToken, генератор понимает, что данные закончились.

Снаружи клиентский код при этом остается простым:

While CustomersGenerator.Next() Do
    ProcessCustomer(CustomersGenerator.Value());
EndDo;

В этом и ценность генератора: клиент обходит поток элементов одинаково, хотя внутри объект может в любой момент:

  • сходить в HTTP;
  • дочитать следующую страницу;
  • остановиться после последней страницы;
  • прекратить работу раньше, если клиенту уже достаточно данных.

Но внутри он уже не берет элемент из готового массива, а сам:

  • вычисляет следующее значение;
  • дочитывает очередную порцию данных;
  • при необходимости завершает выдачу, когда источник исчерпан.

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

Где полезен в 1С

  • когда у одной коллекции нужно несколько вариантов обхода;
  • когда коллекция сложная и не хочется раскрывать ее внутреннюю структуру наружу;
  • когда обход должен быть независимым объектом с собственным состоянием.

Когда паттерн лишний

  • если достаточно обычного Для Каждого;
  • если коллекция не меняет способ хранения;
  • если отдельный объект обхода выглядит тяжелее самой задачи.

Источник примера