Рассматривается поведенческий шаблон проектирования Iterator (Итератор), когда применяется, пример реализации.
Содержание
- Применение
- Реализация
- Итоги
Применение
Назначение шаблона проектирования Iterator — перебор внутренних элементов какого-то объекта-коллекции без раскрытия их внутренней реализации. С его помощью разделяются задачи хранения элементов и процессы их перебора, что упрощает контейнер. Ведь устроены объекты хранения могут быть по разному, да и виды перебора также нужны самые различные. Одному нужен последовательный перебор, другим — по возрастанию, убыванию или случайный доступ и т.п. Так наш контейнер и обрастает вспомогательными методами. Вот тут то и приходит на помощь шаблон Итератор, где перебор элементов вынесен в отдельный класс.
Таким образом, налицо принцип единственной ответственности. Объект коллекции обеспечивает свои обязанности хранения, а классы итераторов обеспечивают различный доступ к его элементам.
Реализация
Для реализации шаблона Iterator проделаем следующее. Создадим класс Container, содержащий массив элементов класса Item. Для доступа к этому массиву наш контейнер будет создавать объект класса Iterator. Кроме того, для примера использования шаблона будет создан клиент, взаимодействующий с объектом контейнера. Таким образом, ниже будут реализованы:
- класс Item — определяет элементы, которые хранятся в контейнере;
- интерфейс IMyIterator — будет определять набор необходимых методов для получения элементов контейнера;
- класс MyIterator — реализация итератора для доступа к элементам контейнера по конкретному алгоритму;
- интерфейс IContainer — декларирует обязательный набор методов для взаимодействия с клиентами;
- класс Container — реализация конкретного контейнера, где будут храниться элементы класса Item;
- класс Client — клиент для взаимодействия с контейнером.
Итак, приступим. Вначале определим класс, описывающий элементы контейнера. В нашем случае это будет единица некоего товара, умеющего возвращать данные о себе.
class Item {
private $name;
private $price;
private $currency;
public function __construct($name, $price, $currency = 'pound') {
$this->name = $name;
$this->price = $price;
$this->currency = $currency;
}
/**
* возвращает строку со своими свойствами
* @return String
*/
public function getProperties() {
return 'name: ' . $this->name . ', price: ' . $this->price . ' ' . $this->currency;
}
}
Для того, чтобы упростить код, элементы контейнера будут представлены в виде массива. Определим интерфейс итератора, который позволит обходить элементы от первого к последнему или наоборот.
interface IMyIterator {
public function getItem(); //возвращает значение текущего элемента
public function getKey(); //возвращает значение текущего ключа
public function next(); //переходит к следующему элементу
public function reset(); //переходит к исходному элементу
}
Теперь очередь класса итератора для обхода элементов массива.
class MyIterator implements IMyIterator {
private $container;
private $index = 0;
private $count;
private $isIncrement = true;
public function __construct($container, $increment = true) {
$this->container = $container;
$this->isIncrement = $increment;
$this->count = count($this->container);
if ($this->isIncrement) {
$this->index = 0;
} else {
$this->index = $this->count - 1;
}
}
public function getItem() {
return $this->container[$this->index];
}
public function getKey() {
return $this->index;
}
public function next() {
$isNext = $this->hasNext();
if ($isNext) {
$this->index = $this->index + ($this->isIncrement ? 1 : -1);
}
}
public function hasNext() {
if ($this->isIncrement) {
return $this->index < $this->count;
} else {
return $this->index >= 0;
}
}
public function reset() {
$this->index = $this->isIncrement ? 0 : $this->count;
}
public function getItems($isInc = true) {
$res = '';
while ($this->hasNext()) {
$item = $this->getItem();
$res .= $item->getProperties() . ";\n";
$this->next();
}
return $res;
}
}
В интерфейсе контейнера декларируем метод, возвращающий объект Итератора.
interface IContainer {
public function createIterator();
}
Теперь займемся контейнером, который хранит массив объектов. Обратите внимание, класс контейнера очень прост, он лишь хранит массив, и умеет возвращать итератор. Такая архитектура позволяет достаточно просто дополнять доступ к элементам с любым алгоритмом перебора. Единственное, в нашем случае при его создании передается параметр $order, определяющий перебор по возрастанию или убыванию.
class Container implements IContainer {
protected $items = array();
protected $order;
public function __construct($goods, $order) {
$this->items = $goods;
$this->order = $order;
}
public function createIterator() {
return new MyIterator($this->items, $this->order);
}
}
Ниже приведен пример доступа клиента к элементам контейнера. Чтобы не загромождать код деталями, положим, что у клиента уже существует экземпляр контейнера с некоторым набором элементов. В этом случае клиенту для получения перечня требуется получить итератор и взаимодействовать с ним.
class Client {
public $goods;
public $isInc = true;
/**
* @var Container $goods
*/
public function __construct($goods) {
$this->goods = $goods;
}
public function setOrder($inc) {
$this->isInc = $inc;
}
public function getPrice() {
$iterator = $this->goods->createIterator();
$price = $iterator->getItems($this->isInc);
return $price;
}
}
Итоги
Итак, кратко повторюсь. Шаблон Iterator полезен в следующих случаях:
- при необходимости произвести обход какой-то коллекции без доступа к его внутренней структуре;
- если требуется обеспечить единый интерфейс различных алгоритмов перебора коллекций;
- в случае, если требуется множество вариантов доступа к элементам хранилища без его усложнения.
Основная идея шаблона — осуществлять перебор коллекции отдельным объектом. За счет этого обеспечивается четкое разделение обязанностей по хранению коллекции и взаимодействию с ней. При этом обеспечивается единый интерфейс для различных алгоритмов перебора и множественный (параллельный) доступ к элементам.
Перейти к списку шаблонов проектирования.