Паттерн “Итератор” (Iterator pattern1 )
Содержание
Введение Необходимость в паттерне “Итератор” возникает при работе с коллекциями. Например чтении строк из из источника (файла, сети…) или просто при необходимости проходить по коллекции.
Данный шаблон настолько популярен, что реализован нативно в C#, C++, Python, Java, JavaScript… Список можно продолжать и дальше.
Ниже представлена UML-диаграмма данного паттерна.
Рассмотрим пример реализации этого паттерна.
Проход по общей коллекции
**C#**
Стоит отметить, что блочные итераторы в C# являются ленивыми подробнее об этом в [[2, с. 75]](#myfootnote2).Определим интерфейс для итератора.
interface IGenericEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
Определим интерфейс для агрегатора.
interface IGenericEnumerable
{
IGenericEnumerator GetEnumerator();
}
Общий итератор.
class GenericEnumerator : IGenericEnumerator
{
private int _currentIndex { get; set; }
private object[] _readOnlyCollection { get; }
public object Current
{
get
{
if (_currentIndex < 0)
throw new InvalidOperationException("Call MoveNext() first!");
return _readOnlyCollection[_currentIndex];
}
}
public GenericEnumerator(object[] readOnlyCollection)
{
_readOnlyCollection = readOnlyCollection;
_currentIndex = -1;
}
public bool MoveNext()
{
if (_currentIndex == _readOnlyCollection.Length-1)
return false;
_currentIndex++;
return true;
}
public void Reset()
{
_currentIndex = 0;
}
}
Общая коллекция.
class GenericCollection : IGenericEnumerable
{
private object[] _array;
public GenericCollection(object[] array)
{
_array = array;
}
public IGenericEnumerator GetEnumerator()
{
return new GenericEnumerator(_array);
}
}
Используем реализованную коллекцию.
class Program
{
static void Main(string[] args)
{
var objectsOfInterest = new object[] { 1, "covfefe", 123.4, true };
var genericCollection = new GenericCollection.GenericCollection(objectsOfInterest);
var genericCollectionEnumerator = genericCollection.GetEnumerator();
Console.WriteLine("Iterating through our GenericCollection");
while (genericCollectionEnumerator.MoveNext())
{
Console.WriteLine(genericCollectionEnumerator.Current);
}
// Здесь мы используем встроеный в C# итератор
var builtInEnumerator = objectsOfInterest.GetEnumerator();
Console.WriteLine("\nIterating through builtin collection");
while (builtInEnumerator.MoveNext())
{
Console.WriteLine(builtInEnumerator.Current);
}
// Здесь мы используем встроеный с версии C# блок итераторов. Отличие тут в типизированности <object>.
Console.WriteLine("\nIterating through builtin collection. (iterator blocks)");
var iteratorBlocksEnumerator = new List<object>(objectsOfInterest).GetEnumerator();
while (iteratorBlocksEnumerator.MoveNext())
{
Console.WriteLine(iteratorBlocksEnumerator.Current);
}
}
}
**Python**
Определим базовый класс ля итератора
class GenericEnumeratorBase:
def current(self) -> object:
raise NotImplemented()
def move_next(self) -> bool:
raise NotImplemented()
def reset(self):
pass
Определим базовый класс для агрегатора.
from GenericColletion.GenericEnumeratorBase import GenericEnumeratorBase
class GenericEnumerableBase:
def get_enumerator(self) -> GenericEnumeratorBase:
raise NotImplemented()
Определим общий итератор
from GenericColletion.GenericEnumeratorBase import GenericEnumeratorBase
class GenericEnumerator(GenericEnumeratorBase):
_readonly_collection = []
_current_index = -1
def __init__(self, readonly_collection: list):
self._readonly_collection = readonly_collection
def current(self) -> object:
if self._current_index < 0:
raise TypeError("Call move_next() first!")
return self._readonly_collection[self._current_index]
def move_next(self) -> bool:
if self._current_index == len(self._readonly_collection) - 1:
return False
self._current_index += 1
return True
def reset(self):
self._current_index = -1
Определим общую коллекцию.
from GenericColletion.GenericEnumerableBase import GenericEnumerableBase
from GenericColletion.GenericEnumeratorBase import GenericEnumeratorBase
from GenericColletion.GenericEnumerator import GenericEnumerator
class GenericCollection(GenericEnumerableBase):
_list_of_interest = []
def __init__(self, list_of_interest: list):
self._list_of_interest = list_of_interest
def get_enumerator(self) -> GenericEnumeratorBase:
return GenericEnumerator(self._list_of_interest)
Используем реализованную коллекцию.
from GenericColletion.GenericCollection import GenericCollection
objectsOfInterest = [1, "covfefe", 123.4, True]
generic_collection = GenericCollection(objectsOfInterest)
generic_collection_enumerator = generic_collection.get_enumerator()
print("Iterating through our GenericCollection")
while generic_collection_enumerator.move_next():
print(generic_collection_enumerator.current())
print("\nIterating through builtin collection")
builtin_iterator = iter(objectsOfInterest)
try:
while True:
print(next(builtin_iterator))
except StopIteration:
print("Builtin iterator reached its end.")
Стоит отметить, что стандартный, реализованный в Python, итератор отличается от классической схемы. Метод next()
возвращает элемент, а не булево значение, позволяющее делать дальше. При достижении конца коллекции бросается ошибка StopIteration
**C**
Далее будет приведена моя реализация этого паттерна на **C**. При знакомстве с ней, требуется помнить, что это демонстрационный код и он не готов к использованию в продакшен среде, однако, как мне кажется, основную идею паттерна в нём передать удалось. С другим примером реализации этого паттерна в **C** можно познакомиться в [[3]](#myfootnote3).Определим вспомогательное перечисление
typedef enum {
INT,
FLOAT,
STRING,
BOOLEAN
} TYPE;
Определим итератор. Для удобства он хранит ссылку на самого себя.
typedef struct IGenericIEnumerator
{
PointerWithElementNumber* _read_only_array;
unsigned int _count;
unsigned int _current_index;
struct IGenericIEnumerator* _self_pointer;
int (*move_next)(struct IGenericIEnumerator*);
PointerWithElementNumber (*current)(struct IGenericIEnumerator*);
void (*reset)(struct IGenericIEnumerator*);
} IGenericIEnumerator;
Общая коллекция. Также хранит ссылку на саму себя.
typedef struct GenericCollection
{
struct GenericCollection* _self_pointer;
PointerWithElementNumber* _array;
unsigned int _count;
IGenericIEnumerator* (*get_enumerator)(struct GenericCollection*);
} GenericCollection;
Реализация движения для конкретного итератора.
int generic_move_next(IGenericIEnumerator* enumerator)
{
if(enumerator->_current_index == enumerator->_count - 1)
return 0;
enumerator->_current_index++;
return 1;
}
Реализация получения текущего элемента коллекции для конкретного итератора.
PointerWithElementNumber generic_current(IGenericIEnumerator *enumerator)
{
if(enumerator->_current_index < 0)
printf("Call move_next() first!");
return enumerator->_read_only_array[enumerator->_current_index];
}
Реализация получения текущего элемента коллекции для конкретного итератора.
PointerWithElementNumber generic_current(IGenericIEnumerator *enumerator)
{
if(enumerator->_current_index < 0)
printf("Call move_next() first!");
return enumerator->_read_only_array[enumerator->_current_index];
}
Реализация сброса итератора.
void generic_reset(IGenericIEnumerator *enumerator)
{
enumerator->_current_index = -1;
}
Реализация получения итератора из коллекции.
IGenericIEnumerator* generic_get_enumerator(GenericCollection* g_c)
{
IGenericIEnumerator* i_g_e = (IGenericIEnumerator*) malloc(sizeof(IGenericIEnumerator));
i_g_e->_self_pointer = i_g_e;
i_g_e->_count = g_c->_count;
i_g_e->_read_only_array = g_c->_array;
i_g_e->_current_index = -1;
i_g_e->current = generic_current;
i_g_e->move_next = generic_move_next;
i_g_e->reset = generic_reset;
return i_g_e;
}
Вывод на экран в зависимости от типа.
void print_pointer_value(PointerWithElementNumber pwen)
{
switch(pwen.type)
{
case INT:
printf("%d\n", *((int*)pwen.pointer));
break;
case FLOAT:
printf("%f\n", *((float*)pwen.pointer));
break;
case STRING:
printf("%s\n", (char*)pwen.pointer);
break;
case BOOLEAN:
printf("%s\n", (*(int*)pwen.pointer) ? "true": "false");
break;
}
}
Теперь инициализируем начальную задачу. (это уже функция main)
unsigned int stringLen = 8;
char* string = (char*) malloc(stringLen*sizeof(char));
strcpy(string, "covfefe\0");
int int_value = 1;
int bool_value = 1;
float float_value = 123.4;
unsigned int array_size = 4;
PointerWithElementNumber* p_w_el_num_array = (PointerWithElementNumber*) malloc(array_size*sizeof(PointerWithElementNumber));
p_w_el_num_array[0].pointer = &int_value;
p_w_el_num_array[0]._count = 1;
p_w_el_num_array[0].type = INT;
p_w_el_num_array[1].pointer = string;
p_w_el_num_array[1]._count = stringLen;
p_w_el_num_array[1].type = STRING;
p_w_el_num_array[2].pointer = &float_value;
p_w_el_num_array[2]._count = 1;
p_w_el_num_array[2].type = FLOAT;
p_w_el_num_array[3].pointer = &bool_value;
p_w_el_num_array[3]._count = 1;
p_w_el_num_array[3].type = BOOLEAN;
Далее инициализируем требуемую коллекцию
GenericCollection generic_collection;
generic_collection.get_enumerator = generic_get_enumerator;
generic_collection._array = p_w_el_num_array;
generic_collection._count = array_size;
generic_collection._self_pointer = &generic_collection;
Осталось пройти по коллекции и вывести её содержимое
IGenericIEnumerator* generic_enumerator = generic_collection.get_enumerator(generic_collection._self_pointer);
while(generic_enumerator->move_next(generic_enumerator))
{
print_pointer_value(generic_enumerator->current(generic_enumerator));
}
Результат работы программы
1
covfefe
123.400002
true
Весь код использованный в проекте доступен на GitHub .
Литература
[1]: Iterator pattern. From Wikipedia [2]: Тепляков С. Паттерны проектирования на платформе .NET – СПб.: Питер, 2015. — 68-83 c. [3]: Implementation of iterator pattern in C [4]: Design Patterns: Elements of Reusable Object-Oriented Software