НЕ МОЛЧИ!!!    Сделай что-нибудь, чтобы остановить войну России в Украине.
...бойтесь людей равнодушных - именно с их молчаливого согласия происходят все самые ужасные преступления на свете.   ("Репортаж с петлёй на шее")

Шаблон проектирования Iterator(Итератор)

Рассматривается поведенческий шаблон проектирования 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 полезен в следующих случаях:

  • при необходимости произвести обход какой-то коллекции без доступа к его внутренней структуре;
  • если требуется обеспечить единый интерфейс различных алгоритмов перебора коллекций;
  • в случае, если требуется множество вариантов доступа к элементам хранилища без его усложнения.

Основная идея шаблона — осуществлять перебор коллекции отдельным объектом. За счет этого обеспечивается четкое разделение обязанностей по хранению коллекции и взаимодействию с ней. При этом обеспечивается единый интерфейс для различных алгоритмов перебора и множественный (параллельный) доступ к элементам.