Patterns. Внешний и внутренний итераторы. Реализация на C++

Внешний и внутренний итераторы. Реализация на C++

Перед изучением данной темы рекомендуется ознакомиться со следующими темами:


Содержание


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




1. Особенности реализации паттерна Iterator. Внешний и внутренний итератор

Управление итератором может выполнять:

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

 

2. Реализация внешнего итератора

Классическая схема паттерна Iterator с использованием внешнего итератора изображена на рисунке 1.

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

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

Более подробно о реализации внешнего итератора на языке C ++ можно прочитать здесь.

 

3. Реализация внутреннего итератора. Пример на C++

Для того, чтобы реализовать внутренний итератор, нужно ввести дополнительный класс-оболочку, который будет реализовывать внешний итератор.

В нижеследующем коде рассматривается динамический массив Aggregate, для которого реализован внутренний итератор. Рассматривается упрощенный вариант с одним контейнером (Aggregate) и одним итератором (Iterator). Полиморфный контейнер и полиморфный итератор не рассматриваются.

Код содержит определения следующих классов:

  • Iterator — класс итератора;
  • Aggregate — класс агрегата, внутри которого реализован динамический массив;
  • AggregateProcess — класс, который обеспечивает реализацию внутреннего итератора. Это базовый класс для подкласса MultItemBy2;
  • MultItemBy2 — класс, переопределяющий операцию ProcessItem() базового класса AggregateProcess. Операция выводит на экран произведение элемента контейнера Aggregate на число 2.

 

#include <iostream>
using namespace std;

// Паттерн Iterator для одного агрегата и одного итератора
// Предварительное объявление класса
template <class T>
class Aggregate;

// Класс итератора
template <class T>
class Iterator
{
private:
  const Aggregate<T>* aggregate;
  long current;

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

  // Переход (перевод курсора) в начало списка
  virtual void First()
  {
    current = 0;
  }

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

  // Проверка, достигнут ли конец списка.
  // Текущая позиция курсора находится за последним элементом списка
  virtual bool IsDone() const
  {
    return current >= aggregate->Count();
  }

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

// Класс агрегата
template <class T>
class Aggregate
{
private:
  // данные агрегата (список, массив, ...)
  T* data;
  long count;

public:
  // Конструктор
  Aggregate(long _count)
  {
    if (_count < 0)
    {
      count = 0;
      data = nullptr;
      return;
    }

    try
    {
      data = new T[_count];
      count = _count;

      for (int i = 0; i < count; i++)
        data[i] = (T)0;
    }
    catch (bad_alloc e)
    {
      cout << e.what() << endl;
      count = 0;
      data = nullptr;
    }
  }

  // Конструктор копирования
  Aggregate(const Aggregate& obj)
  {
    // скопировать данные из obj в текущий экземпляр
    count = obj.count;

    // выделить память для массива в целом
    try
    {
      data = new T[count];
      for (int i = 0; i < count; i++)
        data[i] = obj.data[i];
    }
    catch (bad_alloc e)
    {
      cout << e.what() << endl;
      count = 0;
    }
  }

  // Метод, возвращающий итератор
  Iterator<T>* CreateIterator() const
  {
    return new Iterator<T>(this);
  }

  // Другие методы класса ConcreteAggregate
  long Count() const
  {
    return count;
  }

  T GetItem(long index) const
  {
    if ((index >= 0) && (index < count))
      return data[index];
    return data[0];
  }

  // Метод, добавляющий в конец списка элемент
  void Append(T value)
  {
    T* data2 = data;
    data = new T[count + 1];

    for (int i = 0; i < count; i++)
      data[i] = data2[i];

    data[count++] = value;
    delete[] data2;
  }

  // Удаление элемента из списка,
  // index = 0, 1, ..., count-1
  void Remove(long index)
  {
    if ((index >= 0) && (index < count))
    {
      // Удалить элемент из списка
      T* data2 = data;
      data = new T[count - 1];

      for (int i = 0; i < index; i++)
        data[i] = data2[i];

      for (int i = index + 1; i < count; i++)
        data[i - 1] = data2[i];

      count--;
      delete[] data2;
    }
  }

  // Деструктор
  ~Aggregate()
  {
    if (count > 0)
      delete[] data;
  }

  // Вывод содержимого агрегата
  void Print(string text)
  {
    cout << text << endl;
    for (int i = 0; i < count; i++)
      cout << data[i] << " ";
    cout << endl;
  }
};

// Класс, реализующий внутренний итератор.
// Этот класс есть базовым для подклассов, которые будут реализовывать
// конкретные операции.
template <class T>
class AggregateProcess
{
private:
  // Итератор всередине класса
  Iterator<T> it;

public:
  // Конструктор - создает итератор в классе
  AggregateProcess(Aggregate<T>* ag) : it(ag)
  { }

  // Метод, который обрабатывает все элементы
  bool ProcessItems()
  {
    bool res = false;

    // 1. Перейти на следующий элемент в контейнере
    it.First();

    // 2. Цикл перебора всех элементов
    while (!it.IsDone())
    {
      // 2.1. Обработать текущий элемент, на который указывает итератор it
      res = ProcessItem(it.CurrentItem());

      // 2.2. Если элемент не обработан, то выйти из цикла
      if (!res) break;

      // 2.3. Перейти к следующему элементу
      it.Next();
    }

    // 3. Вернуть результат обработки
    return res;
  }

protected:
  // Виртуальный метод, обрабатывающий один элемент.
  // В подклассах этот метод должен быть заменен
  // конкретной реализацией, которая осуществляет специфическую обработку.
  virtual bool ProcessItem(const T&) = 0;
};

// Подкласс, определяющий операцию обработки отдельного элемента контейнера.
// Этот подкласс наследует класс AggregateProcess, для того
// чтобы переопределить операцию ProcessItem ().
template <class T>
class MultItemBy2 : public AggregateProcess<T>
{
public:
  // Конструктор
  MultItemBy2(Aggregate<T>* ag) : AggregateProcess<T>(ag) { }

protected:
  // Метод обработки одного элемента контейнера
  bool ProcessItem(const T& item)
  {
    // Вывести элемент, умноженный на 2
    cout << item * 2 << " ";

    // Обработка прошла успешно
    return true;
  }
};

void main()
{
  // 1. Создать агрегат
  Aggregate<double> ag(0);
  ag.Append(2.55);
  ag.Append(3.8);
  ag.Append(-1.557);
  ag.Append(7.32);

  // 2. Создать внутренний итератор, который
  //     будет выводить на экран каждый элемент контейнера
  //     умноженный на 2
  MultItemBy2<double> it(&ag);
  bool res = it.ProcessItems();
  if (res == true)
    cout << endl << "Ok!" << endl;
  else
    cout << endl << "Error" << endl;
}

 


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