Итератор — это поведенческий паттерн проектирования, который даёт возможность последовательно обходить элементы составных объектов, не раскрывая их внутреннего представления.
Реализация на Phyton
Как реализовать паттерн проектирования «Итератор» и подключиться к встроенным итерационным механизмам языка Python for
, iter()
и next()
?
__iter__()
, который возвращает объект итератора. Поддержка этого метода делает контейнер итерируемым.__next__()
(в старом коде Python 2 next()
записывали без двойного подчёркивания), который возвращает следующий элемент из контейнера при каждом вызове. Бросайте исключение StopIterator
, когда больше нет элементов.for
итераторы вместо основного контейнера? Чтобы обезопаситься в этом случае, каждому итератору также нужен метод __iter__()
, который возвращает сам себя.Посмотрите, как эти требования работают вместе, на примере нашего собственного итератора!
Обратите внимание, что не требуется, чтобы элементы, полученные в результате __next__()
, сохранялись как постоянные значения внутри контейнера или даже присутствовали до вызова __next__()
. Значит написать пример паттерна проектирования «Итератор» можно даже без реализации хранилища в контейнере:
class OddNumbers(object): "An iterable object." def __init__(self, maximum): self.maximum = maximum def __iter__(self): return OddIterator(self) class OddIterator(object): "An iterator." def __init__(self, container): self.container = container self.n = -1 def __next__(self): self.n += 2 if self.n > self.container.maximum: raise StopIteration return self.n def __iter__(self): return self
Благодаря этим трём методам – одному для объекта-контейнера и двум для его итератора – контейнер OddNumbers
теперь полноправно участвует в богатой итерационной экосистеме языка программирования Python. Он будет работать без проблем с циклом for
:
numbers = OddNumbers(7) for n in numbers: print(n) 1 3 5 7
И также работает со встроенными методами iter()
и next()
.
it = iter(OddNumbers(5)) print(next(it)) print(next(it)) 1 3
А также взаимодействует с генераторами списков и множеств!
print(list(numbers)) print(set(n for n in numbers if n > 4)) [1, 3, 5, 7] {5, 7}
Реализация на PHP
namespace RefactoringGuru\Iterator\RealWorld; /** * Итератор CSV-файлов. * * @author Josh Lockhart */ class CsvIterator implements \Iterator { const ROW_SIZE = 4096; /** * Указатель на CSV-файл. * * @var resource */ protected $filePointer = null; /** * Текущий элемент, который возвращается на каждой итерации. * * @var array */ protected $currentElement = null; /** * Счётчик строк. * * @var int */ protected $rowCounter = null; /** * Разделитель для CSV-файла. * * @var string */ protected $delimiter = null; /** * Конструктор пытается открыть CSV-файл. Он выдаёт исключение при ошибке. * * @param string $file CSV-файл. * @param string $delimiter Разделитель. * * @throws \Exception */ public function __construct($file, $delimiter = ',') { try { $this->filePointer = fopen($file, 'rb'); $this->delimiter = $delimiter; } catch (\Exception $e) { throw new \Exception('The file "' . $file . '" cannot be read.'); } } /** * Этот метод сбрасывает указатель файла. */ public function rewind(): void { $this->rowCounter = 0; rewind($this->filePointer); } /** * Этот метод возвращает текущую CSV-строку в виде двумерного массива. * * @return array Текущая CSV-строка в виде двумерного массива. */ public function current(): array { $this->currentElement = fgetcsv($this->filePointer, self::ROW_SIZE, $this->delimiter); $this->rowCounter++; return $this->currentElement; } /** * Этот метод возвращает номер текущей строки. * * @return int Номер текущей строки. */ public function key(): int { return $this->rowCounter; } /** * Этот метод проверяет, достигнут ли конец файла. * * @return bool Возвращает true при достижении EOF, в ином случае false. */ public function next(): bool { if (is_resource($this->filePointer)) { return !feof($this->filePointer); } return false; } /** * Этот метод проверяет, является ли следующая строка допустимой. * * @return bool Если следующая строка является допустимой. */ public function valid(): bool { if (!$this->next()) { if (is_resource($this->filePointer)) { fclose($this->filePointer); } return false; } return true; } } /** * Клиентский код. */ $csv = new CsvIterator(__DIR__ . '/cats.csv'); foreach ($csv as $key => $row) { print_r($row); }