Итератор (Iterator)¶
Iterator позволяет обходить коллекцию, не раскрывая клиенту ее внутреннее устройство.
Опора на ООП¶
Iterator опирается на инкапсуляцию и интерфейсы: внутренняя структура коллекции скрыта, а наружу выставлен только контракт обхода. Полиморфизм полезен, когда у одной коллекции есть несколько взаимозаменяемых обходчиков.
Что показывает пример на 1С¶
DataProcessors.WordsCollectionскрывает внутреннийArrayи отдает обходчик черезIterator(Reverse = False).DataProcessors.AlphabeticalOrderIteratorхранитCollection,Position,Reverseи предоставляет методыNext()иValue().- Клиент работает с обходом как с отдельным объектом и не зависит от способа хранения коллекции.
Пример¶
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#, поэтому генератор обычно оформляется как обычный объект с состоянием. Снаружи он нередко выглядит почти так же, как итератор:
Практический пример для 1С: получение списка через HTTP API с пагинацией.
Представим, что внешний сервис отдает клиентов не целиком, а страницами по 100 записей:
- первый запрос получает первую страницу и
nextPageToken; - второй запрос получает следующую страницу по этому токену;
- и так далее, пока страницы не закончатся.
Если собирать все страницы заранее, то внешний код сначала скачает весь список, потом сложит его в Array или ValueTable, и только после этого начнет обработку. Для больших выборок это лишняя память и лишняя задержка.
Генератор позволяет оформить это лениво:
- объект хранит
HttpClient,NextPageToken, текущий буфер элементов и признак завершения; - при
Next()он проверяет, есть ли элемент в текущем буфере; - если буфер пуст, делает следующий HTTP-запрос, заполняет буфер новой страницей и только потом отдает очередной элемент;
- если сервис больше не вернул
nextPageToken, генератор понимает, что данные закончились.
Снаружи клиентский код при этом остается простым:
В этом и ценность генератора: клиент обходит поток элементов одинаково, хотя внутри объект может в любой момент:
- сходить в HTTP;
- дочитать следующую страницу;
- остановиться после последней страницы;
- прекратить работу раньше, если клиенту уже достаточно данных.
Но внутри он уже не берет элемент из готового массива, а сам:
- вычисляет следующее значение;
- дочитывает очередную порцию данных;
- при необходимости завершает выдачу, когда источник исчерпан.
Поэтому генератор можно считать ленивой вариацией итератора. В терминах архитектуры разница простая: итератор обходит, генератор порождает.
Где полезен в 1С¶
- когда у одной коллекции нужно несколько вариантов обхода;
- когда коллекция сложная и не хочется раскрывать ее внутреннюю структуру наружу;
- когда обход должен быть независимым объектом с собственным состоянием.
Когда паттерн лишний¶
- если достаточно обычного
Для Каждого; - если коллекция не меняет способ хранения;
- если отдельный объект обхода выглядит тяжелее самой задачи.