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;
}

 


Зв’язані теми