Паттерн "Итератор"

2017-06-16 • edited 2021-02-06

Паттерн “Итератор” (Iterator pattern1 )

Содержание

Введение Необходимость в паттерне “Итератор” возникает при работе с коллекциями. Например чтении строк из из источника (файла, сети…) или просто при необходимости проходить по коллекции.

Данный шаблон настолько популярен, что реализован нативно в C#, C++, Python, Java, JavaScript… Список можно продолжать и дальше.

Ниже представлена UML-диаграмма данного паттерна.

stragUml

Рассмотрим пример реализации этого паттерна.

Проход по общей коллекции

Реализации

**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

design patternCsharpPythonCповеденческий шаблонbehavioralpatternIterator
License: MIT

Паттерн "Посредник"

Шаблоны проектирования

comments powered by Disqus