Итератор (Iterator) — шаблон проектирования
31 января 2020
Шаблон предлагает схему для обхода списка элементов. Идея паттерна заключается в том, чтобы за реализацию обхода элементов отвечал отдельный класс-итератор, а не клиентский код или методы класса элементов (контейнера элементов). Это даёт возможность в случае изменения класса элементов сохранить клиентский код и сделать общую реализацию гибкой.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 4 Кб)
В PHP есть готовые классы, реализующие итераторы https://www.php.net/manual/ru/spl.iterators.php, а также есть языковая конструкция foreach для обхода элементов массива и публичных свойства объекта. В рамках данной статьи, встроенные в PHP итераторы и конструкция foreach использоваться не будут.
Абстрактный класс AbstractIterator служит для объявления интерфейса итераторов. Содержит следующие свойства и методы:
Класс IteratorObserverEvents унаследован от абстрактного класса AbstractIterator и представляет конкретную реализацию итератора для обхода событий, находящихся под управлением наблюлателя Observer. Помимо родительских свойств и методов, класс дополнительно раскрывает следующие свойства и методы:
Затем происходит срабатывание события PageShow с помощью метода:
Тем самым, мы заставляем выполнить обработчики этих событий. Обход событий осуществляется напрямую в методе event($name) класса Observer.
Далее снова вызываем событие PageShow, но уже с помощью метода:
Данный метод eventByIterator($name) для обхода событий использует итератор IteratorObserverEvents. С его помощью клиентский код обхода сводится к созданию объекта итератора и получению элементов события методом $iteratorEvents->getNext()
Таким образом, всю логику обхода событий наблюдателя Observer мы спрятали в класс-итератор IteratorObserverEvents.
Результат работы скрипта test.php
Помимо архитектурной гибкости, шаблон «Итератор» позволяет для одной и той же очереди, одного и того же массива применять разные способы обхода, реализуя для каждого из способов свой собственный итератор.
ПРИМЕР ЗАДАЧИ: обойти очередь событий и вызвать обработчики для определённого вида событий.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 4 Кб)
В PHP есть готовые классы, реализующие итераторы https://www.php.net/manual/ru/spl.iterators.php, а также есть языковая конструкция foreach для обхода элементов массива и публичных свойства объекта. В рамках данной статьи, встроенные в PHP итераторы и конструкция foreach использоваться не будут.
event.class.php
Содержит класс Event, представляющее событие. Состоит из следующих свойств и методов:- $name – название события. Название определяет тип события. Например, название PageShow соответствует событию загрузки страницы, а MailSended соответствует событию отправки письма;
- $handler – обработчик события. В момент срабатывания события вызывается данный обработчик;
- $nextEvent – ссылка на следующий объект события. Ссылка используется для связывания событий в цепочку;
- Конструктор __construct($name, $handler) – в момент создания объекта задаёт имя события и обработчик;
- getName() – возвращает название события;
- handle() – запускает выполнение обработчика события;
- setNextEvent($nextEvent) – устанавливает ссылку на следующий объект события. Используется для связывания событий в цепочку.
<?
/**
* Событие
*/
class Event
{
/**
* Название события
* @var string
*/
private $name = '';
/**
* Обработчик события
* @var callable
*/
private $handler = null;
/**
* Ссылка на следующее событие. Используется для связи событий в очередь
* @var Event
*/
public $nextEvent = null;
/**
* Конструктор. Создаёт объект события
* @param string $name - название события
* @param string $handler - обработчик события (функция, вызываемая при срабатывании события)
*/
public function __construct($name, $handler)
{
$this->name = $name;
$this->handler = $handler;
}
/**
* Возвращает название события
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Выполнить обработчик
*/
public function handle()
{
if (is_callable($this->handler)) {
call_user_func($this->handler);
} else {
throw new Exception('Ошибка. У события «'.$this->name.'» не задан обработчик (функция обратного вызова)');
}
}
/**
* Установка ссылки на следующее событие. Используется для связывания событий в цепочку
* @param Event $nextEvent
*/
public function setNextEvent($nextEvent)
{
$this->nextEvent = $nextEvent;
}
}
?>
observer.class.php
Представляет класс наблюдателя событий Observer. Содержит следующие свойства и методы:- $firstEvent – хранит ссылку на первое событие в очереди (цепочке) событий;
- $lastEvent – хранит ссылку на последнее событие в очереди событий;
- addEvent($name, $handler) – добавляет событие в очередь наблюдателя. В аргументе $name указывается название события, а в $handler задаётся его обработчик;
- event($name) – запускает срабатывание обработчиков события, название которого задано в аргументе $name. Метод реализует свой собственный обход очереди событий;
- eventByIterator($name) – аналогично, вызывает срабатывание обработчиков события, название которого задано в аргументе $name. Метод реализует обход очереди с помощью класса-итератора IteratorObserverEvents;
<?
//Подключаем класс для работы с событиями
require_once(__DIR__.'/event.class.php');
/**
* Наблюдатель событий (служит для учёта и работы с событиями)
*/
class Observer
{
/**
* Ссылка на первое событие в очереди
* @var Event
*/
public $firstEvent = null;
/**
* Ссылка на последнее событие в очереди
* @var Event
*/
public $lastEvent = null;
/**
* Добавляет событие в наблюдателя
* @param string $name - имя события
* @param callable $handler - обработчик события
*/
public function addEvent($name, $handler)
{
$event = new Event($name, $handler);
//Установка ссылки на первый элемент списка
if (is_null($this->firstEvent)) {
$this->firstEvent = $event;
}
//Если в очереди есть события, то у прошлого последнего события ставим в указатель $nextEvent ссылку на новое последнее событие
if (is_null($this->lastEvent) == false) {
$this->lastEvent->setNextEvent($event);
}
//Обновляем ссылку на последнее событие
$this->lastEvent = $event;
}
/**
* Срабатывание определённых событий, находящихся в очереди
* @param string $name - имя события, которое нужно вызвать
*/
public function event($name)
{
$currentEvent = $this->firstEvent;
while (is_object($currentEvent) == true) {
//Если имя текущего события совпадает с запрашиваемым, то запускаем его обработчик
if ($currentEvent->getName() == $name) {
$currentEvent->handle();
}
$currentEvent = $currentEvent->nextEvent;
}
}
/**
* Срабатывание определённых событий, находящихся в очереди
* @param string $name - имя события, которое нужно вызвать
*/
public function eventByIterator($name)
{
//Подключаем классы для работы с итераторами
require_once(__DIR__.'/iterators.classes.php');
$iteratorEvents = new IteratorObserverEvents($this);
while($currentEvent = $iteratorEvents->getNext()) {
//Если имя текущего события совпадает с запрашиваемым, то запускаем его обработчик
if ($currentEvent->getName() == $name) {
$currentEvent->handle();
}
}
}
}
?>
iterators.classes.php
Содержит абстрактный класс итератора AbstractIterator и конкретный итератор в виде класса IteratorObserverEvents.Абстрактный класс AbstractIterator служит для объявления интерфейса итераторов. Содержит следующие свойства и методы:
- $firstItem – содержит ссылку на первый элемент в цепочке элементов;
- $currentItem – содержит ссылку на текущий элемент из процесса обхода элементов;
- currentItem() – возвращает текущий элемент;
- reset() – сбрасывает указатель текущего элемента, устанавливая его на первый элемент очереди элементов;
- next() – абстрактный метод. Переводит указатель текущего элемента на следующий элемент. Метод вернёт true, если указатель удалось перевести и false в противном случае;
- isEnd() – абстрактный метод. Проверяет, достигнут ли конец цепочки элементов. Метод вернёт true, если достигнут конец цепочки элементов и false в противном случае.
Класс IteratorObserverEvents унаследован от абстрактного класса AbstractIterator и представляет конкретную реализацию итератора для обхода событий, находящихся под управлением наблюлателя Observer. Помимо родительских свойств и методов, класс дополнительно раскрывает следующие свойства и методы:
- $observer – содержит ссылку на объект наблюдателя событий Observer;
- Конструктор __construct($observer) – устанавливает ссылку на объект наблюдателя событий Observer и ставит указатель первого обходимого элемента (события) на первый элемент очереди событий из объекта $observer;
- reset() – сбрасывает указатель текущего элемента, устанавливая его в значение null. Также обновляет указатель первого элемента цепочки, ставя его на первый элемент цепочки из объекта наблюдателя событий, находящегося в свойстве $observer;
- next() – переводит указатель текущего элемента на следующий элемент. Метод вернёт true, если указатель удалось перевести и false в противном случае. Здесь, в отличие от абстрактного класса, метод получил конкретную реализацию;
- isEnd() – проверяет, достигнут ли конец цепочки элементов. Метод вернёт true, если достигнут конец цепочки элементов и false в противном случае. Метод также получил конкретную реализацию;
- getNext() – передвигает указатель текущего элемента на следующий элемент (внутри вызывается метод next()) и возвращает его. Метод вернёт элемент или false, если следующие элементы (события) отсутствуют, либо цепочка пустая.
<?
/**
* Абстрактный класс итератора
*/
abstract class AbstractIterator
{
/**
* Ссылка на первый элемент в цепочке элементов
* @var mixed
*/
protected $firstItem = null;
/**
* Ссылка на текущий элемент из процесса обхода элементов
* @var mixed
*/
protected $currentItem = null;
/**
* Возвращает текущий элемент
* @return mixed
*/
public function currentItem()
{
return $this->currentItem;
}
/**
* Сбрасывает указатель текущего элемента, устанавливая его на первый элемент очереди элементов
*/
public function reset()
{
$this->currentItem = $this->firstItem;
}
/**
* Переводит указатель текущего элемента на следующий элемент
* @return bool - вернёт 'true', если указатель удалось перевести и 'false' в противном случае
*/
abstract public function next();
/**
* Проверяет, достигнут ли конец цепочки элементов
* @return bool - вернёт 'true', если достигнут конец цепочки элементов и 'false' в противном случае
*/
abstract public function isEnd();
}
/**
* Итератор для обхода событий, находящихся под управлением класса Observer
*/
class IteratorObserverEvents extends AbstractIterator
{
/**
* Содержит ссылку на объект класса Observer
* @var Observer
*/
private $observer = null;
/**
* Конструктор. Устанавливает ссылку на объект наблюдателя событий Observer и ставит
* ссылку на первый элемент очереди событий.
* @param Observer $observer
*/
public function __construct($observer)
{
$this->observer = $observer;
$this->firstItem = $this->observer->firstEvent;
}
/**
* Сбрасывает указатель текущего элемента, устанавливая его в значение null.
* Также обновляет указатель первого элемента цепочки, ставя его на первый
* элемент из объекта наблюдателя событий Observer
*/
public function reset()
{
$this->firstItem = $this->observer->firstEvent;
$this->currentItem = null;
}
/**
* Передвигает указатель текущего элемента на следующий элемент в очереди событи
* @return bool - вернёт 'true' в случае успешного передвижения и 'false' в противном случае
*/
public function next()
{
//Если не достигнут конец очереди, то передвигает указатель текущего элемента на следующий элемент
if ($this->isEnd() == false) {
//Если текущий элемент является объектом, то используем у него ссылку на следующий элемент
if (is_object($this->currentItem) == true) {
$this->currentItem = $this->currentItem->nextEvent;
return true;
}
/*
Если текущий элемент НЕ является объектом, то предполагается, что мы находимся в начале цепочки.
Тогда сбрасываем указатели и указатель текущего элемента передвигает на первый элемент цепочки элементов
*/
else {
$this->reset();
if (is_object($this->firstItem) == false) {
return false;
} else {
$this->currentItem = $this->firstItem;
return true;
}
}
} else {
return false;
}
}
/**
* Проверяет, достигнут ли конец цепочки элементов
* @return bool - вернёт 'true', если достигнут конец цепочки элементов и 'false' в противном случае
*/
public function isEnd()
{
if (is_object($this->currentItem) == true && is_object($this->currentItem->nextEvent) == false) {
return true;
} elseif (is_object($this->firstItem) == false && is_object($this->currentItem) == false) {
return true;
} else {
return false;
}
}
/**
* Передвигает указатель текущего элемента на следующий элемент и возвращает его
* @return mixed - вернёт элемент цепочки элементов или 'false', если следующих элементов нету или цепочка пустая
*/
public function getNext()
{
if ($this->isEnd() == false) {
$this->next();
return $this->currentItem;
} else {
return false;
}
}
}
?>
test.php
Содержит тестовый скрипт, в котором создаётся объект наблюдателя событий $observer и затем в него добавляются пять событий – три события PageShow и два события MailSended. В обработчике каждого события мы выводим на экран определённое сообщение.Затем происходит срабатывание события PageShow с помощью метода:
$observer->event('PageShow');
Тем самым, мы заставляем выполнить обработчики этих событий. Обход событий осуществляется напрямую в методе event($name) класса Observer.
Далее снова вызываем событие PageShow, но уже с помощью метода:
$observer->eventByIterator('PageShow');
Данный метод eventByIterator($name) для обхода событий использует итератор IteratorObserverEvents. С его помощью клиентский код обхода сводится к созданию объекта итератора и получению элементов события методом $iteratorEvents->getNext()
$iteratorEvents = new IteratorObserverEvents($this);
while($currentEvent = $iteratorEvents->getNext()) {
//Если имя текущего события совпадает с запрашиваемым, то запускаем его обработчик
if ($currentEvent->getName() == $name) {
$currentEvent->handle();
}
}
Таким образом, всю логику обхода событий наблюдателя Observer мы спрятали в класс-итератор IteratorObserverEvents.
Результат работы скрипта test.php
Вариант обхода событий без отдельного класса-итератора
-----------------------
Показ страницы (событие 1)
Показ страницы (событие 2)
Показ страницы (событие 3)
Вариант обхода событий с отдельным классом-итератором
-----------------------
Показ страницы (событие 1)
Показ страницы (событие 2)
Показ страницы (событие 3)
Помимо архитектурной гибкости, шаблон «Итератор» позволяет для одной и той же очереди, одного и того же массива применять разные способы обхода, реализуя для каждого из способов свой собственный итератор.