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

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

Дана тема є продовженням теми:


Зміст


Пошук на інших ресурсах:




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

На рисунку 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;
    }
  }
};

За вищенаведеним зразком можна створити власний ітератор, який буде обходити елементи контейнера деяким специфічним чином.

 


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