Patterns. Паттерн Iterator. Особенности реализации на C++

Паттерн Iterator. Особенности реализации на C++ для полиморфного контейнера и полиморфного итератора

Данная тема есть продолжением темы:


Содержание


Поиск на других ресурсах:




1. Структура паттерна Iterator в кодах языка C++. Рисунок

На рисункe 1 изображена структура паттерна Iterator для некоторого контейнерного класса (список, динамический массив, очередь и т.д.). Рисунок отражает структуру для любого количества контейнеров (полиморфный контейнер) и любого количества итераторов (полиморфный итератор). Из всех возможных структур, эта структура является наиболее универсальной.

В учебных целях, к каждому блоку структуры добавляется фрагмент его реализации на языке C++.

Паттерн Iterator. Схема с реализацией на C++

Рисунок 1. Схема паттерна Iterator с реализацией на языке C++

  

2. Составляющие паттерна Iterator. Минимальный набор классов для реализации паттерна на C++, который поддерживает несколько контейнеров и несколько итераторов

Структурная схема паттерна Iterator, обеспечивающего поддержку нескольких контейнеров и нескольких итераторов изображена на рисунке 4. Для этой реализации паттерна нужно разработать следующие классы.

2.1. Абстрактный класс Aggregate

Абстрактный класс Aggregate определяет интерфейс контейнера. Как правило, этот класс содержит объявления чисто виртуального метода CreateIterator() для возврата итератора, которым служит абстрактный класс Iterator.

// Абстрактный класс агрегата (интерфейс)
template <class T>
class Aggregate
{
public:
  // Метод, который будет возвращать итератор для некоторого списка
  virtual Iterator<T>* CreateIterator() const = 0;
};

  

2.2. Абстрактный класс Iterator

Абстрактный класс Iterator определяет интерфейс итератора. Здесь можно указать базовые методы обхода по нижеследующему образцу

// Абстрактный класс итератора
template <class T>
class Iterator
{
public:
  // Переход (перевод курсора) в начало списка
  virtual void First() = 0;

  // Переход (перевод курсора) на следующий элемент списка
  virtual void Next() = 0;

  // Проверка, достигнут ли конец списка,
  // текущая позиция курсора находится за последним элементом списка
  virtual bool IsDone() const = 0;

  // Получить элемент списка по текущей позиции курсора
  virtual T CurrentItem() const = 0;
};

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

  

3. Один или несколько конкретных контейнеров

Любой контейнер есть классом, реализующим одну из агрегированных структур данных (список, динамический массив, очередь и т.п.). В нашем случае (рисунок 2) имя контейнера является обобщенным и определено как ConcreteAggregate. Однако, в реальных программах, имя может быть изменено на более понятное (List, Array, Vector, Map, Queue и т.д.).
Класс ConcreteAggregate реализует интерфейсный класс Aggregate. Это значит, что в классе ConcreteAggregate реализуется метод CreateIterator().
Код контейнерного класса может быть примерно таким

// Конкретный агрегат - реализует интерфейс Aggregate
template <class T>
class ConcreteAggregate : public Aggregate<T>
{
private:
  // данные агрегата (список, массив, ...)
  // ...

public:
  Iterator<T>* CreateIterator() const;

  // Другие методы класса ConcreteAggregate
  // ...
};

Например, для динамического массива сокращенная версия кода на языке C++ может быть такой

// Конкретный динамический массив
template <class T>
class Array : public Aggregate<T>
{
private:
  // Внутренние переменные
  T* array; // данные в виде динамического массива
  long count; // количество элементов в массиве

public:
  // Конструктор
  Array(long size);

  // Конструктор копирования
  Array(const Array& obj);

  // Возвращает количество элементов в коллекции
  long Count() const;

  // Получить элемент массива по заданному индексу,
  // если индекс неправильно указан, то функция возвращает nullptr
  T& Get(long index) const
  {
    ...
  }

  // Реализация метода, создающего итератор
  Iterator<T>* CreateIterator() const
  {
    ...
  }

  // Реализация метода, добавляющего элемент в массив
  void Append(T value)
  {
    ...
  }

  // Удаление элемента из массива, index = 0, 1, ..., count-1
  void Remove(long index)
  {
    ...
  }

  // Деструктор
  ~Array()
  {
    ...
  }

  // Другие составляющие класса
  // ...
};

  

4. Один или несколько конкретных итераторов

Для любого агрегированного объекта (контейнера) может быть реализовано различные стратегии обхода его элементов. Конкретный итератор реализует одну из этих стратегий

// Конкретный итератор
template <class T>
class ConcreteIterator : public Iterator<T>
{
private:
  const ConcreteAggregate<T>* aggregate;
  long current;

public:
  // Конструктор, получающий агрегированный объект
  ConcreteIterator(const ConcreteAggregate<T>* _aggregate) :
    aggregate(_aggregate), current(0)
  { }

  // На первый элемент
  void First()
  {
    current = 0;
  }

  // На следующий элемент
  void Next()
  {
    current++;
  }

  // Проверка, конец ли списка
  bool IsDone() const
  {
    return current >= aggregate->Count();
  }

  // Выводит текущий элемент
  T CurrentItem() const
  {
    if (!IsDone())
      return aggregate->GetItem(current);
    else
    {
      // Здесь ошибка, можно сгенерировать исключение или
      // выполнить другие действия
      cout << "Error." << endl;
      return 0;
    }
  }
};

По вышеприведенному образцу можно создать собственный итератор, который будет обходить элементы контейнера некоторым специфическим образом.

  


Связанные темы