Patterns. Паттерн Iterator. Реалізація на Java




Паттерн Iterator. Реалізація на Java з підтримкою поліморфного контейнера та поліморфного ітератора

Перед вивченням даної теми рекомендується ознайомитись з наступною темою:


Зміст


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

1. Структурна схема паттерну Iterator з прив’язкою до коду Java

На рисунку зображено схему паттерну Iterator з підтримкою поліморфного контейнера та поліморфного ітератора.

Схема паттерну Iterator з прив’язкою до Java-коду

Рисунок. Схема паттерну Iterator з прив’язкою до Java-коду

 

2. Програмна реалізація
2.1. Класи, необхідні для демонстрації паттерну

Для реалізації паттерну Iterator, зображеного на рисунку 1, потрібен наступний мінімальний набір інтерфейсів та класів:

  • узагальнений інтерфейс IAggregate<T> – визначає базові методи, необхідні для реалізації контейнера (агрегату). Цей інтерфейс будуть реалізовувати (implements) класи конкретних контейнерів (списків, масивів тощо). Завдяки поліморфізму для клієнта будуть підвантажуватись методи з відповідного конкретного контейнера;
  • узагальнений інтерфейс IIterator<T> – визначає перелік методів, необхідних для забезпечення роботи ітератора. Кожен конкретний ітератор буде реалізований в класах, що реалізують цей інтерфейс;
  • один або декілька узагальнених класів, які реалізують інтерфейс IAggregate<T>. Ці класи представляють конкретні контейнери. Кожен з контейнерів представляє собою деякий набір елементів: масив, список тощо. Більш детальну інформацію про види контейнерних класів можна отримати тут. У нашому випадку демонструється розробка одного узагальненого класу з іменем ConcreteAggregate<T>;
  • один або декілька узагальнених класів, які реалізують інтерфейс IIterator<T>. Ці класи є ітераторами. Як відомо, для визначеного контейнера може бути декілька ітераторів, наприклад прямий ітератор, зворотній ітератор тощо. Більш детально про класифікацію ітераторів можна прочитати тут;
  • клас клієнта, який буде демонструвати роботу конкретного ітератора для конкретного контейнера.

 

2.2. Інтерфейс IAggregate<T>. Спільний інтерфейс для всіх агрегатів

Першим у програмі оголошується інтерфейс, що є спільним для усіх класів агрегатів (контейнерів або контейнерних класів). Агрегатами можуть бути списки, масиви, множини тощо.

У нашому випадку оголошується інтерфейс IAggregate<T>. Тут T – ім’я деякого узагальненого типу.

Текст модуля TestIterator.java, що демонструє роботу паттерну, поки що має наступний вигляд.

// Паттерн Iterator. Реалізація на Java
// Інтерфейс контейнера (агрегату)
interface IAggregate<T> {
  // Метод, що повертає ітератор для контейнера
  IIterator<T> CreateIterator();

  // Метод, що повертає кількість елементів у контейнері
  int Count();

  // Метод, що повертає елемент в контейнері за його позицією index
  T GetItem(int index);
}

public class TestIterator {
  public static void main(String[] args) {
  }
}

 

2.3. Інтерфейс IIterator<T>. Узагальнений інтерфейс ітератора

На цьому кроці в програму додається інтерфейс IIterator<T>. Цей інтерфейс повинен бути реалізований в класах конкретних ітераторів.

Після вводу в програму інтерфейсу, скорочений текст програмного модуля наступний.

// Паттерн Iterator. Реалізація на Java
// Інтерфейс контейнера (агрегату)
interface IAggregate<T> {
  ...
}

// Інтерфейс ітератора
interface IIterator<T> {
  // Перемотати курсор на початок набору
  void First();

  // Перемотати курсор на наступний елемент
  void Next();

  // Повернути елемент на який вказує курсор
  T CurrentItem();

  // Перевірка, чи курсор вказує на позицію,
  // що знаходиться за останнім елементом набору
  boolean IsDone();
}

public class TestIterator {
  public static void main(String[] args) {
  }
}

 

2.4. Клас конкретного ітератора ConcreteIterator1<T>

Будь-який клас конкретного ітератора повинен реалізовувати інтерфейс IIterator<T>. У нашому випадку клас конкретного ітератора має ім’я ConcreteIterator1<T>. У класі конкретного ітератора оголошуються наступні елементи:

  • внутрішнє поле aggregate – посилання на конкретний контейнер;
  • внутрішнє поле current – курсор, який вказує на деякий елемент в контейнері;
  • конструктор, який отримує посилання на інтерфейс IAggregate<T>;
  • метод First() – переміщує курсор на перший елемент контейнера (елемент в позиції 0);
  • метод Next() – реалізує переміщення курсору на одну позицію вперед;
  • метод IsDone() – визначає, чи курсор current вказує на позицію, що встановлена за останнім елементом;
  • метод CurrentItem() – повертає елемент, на який вказує current.

Після додавання класу ConcreteIterator1<T> скорочений текст програми наступний:

// Паттерн Iterator. Реалізація на Java
// Інтерфейс контейнера (агрегату)
interface IAggregate<T> {
  ...
}

// Інтерфейс ітератора
interface IIterator<T> {
  ...
}

// Клас конкретного ітератора
class ConcreteIterator1<T> implements IIterator<T> {
  // Внутрішні поля класу
  private final IAggregate<T> aggregate; // посилання на контейнер
  private int current; // поточна позиція курсору

  // Конструктор для ітератора
  public ConcreteIterator1(IAggregate<T> agg) {
    aggregate = agg;
    current = 0;
  }

  // Реалізація методів, оголошених в інтерфейсі IIterator<T>
  // Переміщення курсору на перший елемент
  public void First() {
    current = 0;
  }

  // Переміщення курсору на наступний елемент
  public void Next() {
    current++;
  }

  // Перевірка, чи кінець набору елементів
  public boolean IsDone() {
    return current >= aggregate.Count();
  }

  // Повернути поточний елемент
  public T CurrentItem() {
    if (!IsDone())
      return aggregate.GetItem(current);
    else
      throw new ArrayIndexOutOfBoundsException("Error.");
  }
}

public class TestIterator {
  public static void main(String[] args) {
  }
}

 

2.5. Клас конкретного агрегату

Усі класи контейнерів повинні реалізовувати інтерфейс IAggregate<T>. У нашому випадку створюється клас контейнера ConcreteAggregate1<T>, який представляє собою динамічний масив типу ArrayList<T>. Щоб використовувати масив ArrayList<T> потрібно імпортувати модуль java.util.ArrayList

import java.util.ArrayList;

Клас містить наступні складові:

  • внутрішнє поле array, що є динамічним масивом типу ArrayList<T>;
  • конструктор, який ініціалізує внутрішній масив array іншим масивом типу T[]. За бажанням, можна реалізувати інші конструктори;
  • метод Append(), який додає елемент в кінець контейнера;
  • метод Remove(), який видаляє елемент з контейнера за його індексом;
  • метод Print(), який друкує елементи масиву;
  • метод CreateIterator() – реалізує однойменний метод інтерфейсу IAggregate<T>. Метод повертає посилання на інтерфейс IIterator<T>;
  • метод Count() – реалізація методу з інтерфейсу IAggregate<T>. Метод повертає кількість елементів у контейнері;
  • метод GetItem() – повертає поточний елемент у контейнері за його позицією.

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

import java.util.ArrayList;

// Паттерн Iterator. Реалізація на Java
// Інтерфейс контейнера (агрегату)
interface IAggregate<T> {
  ...
}

// Інтерфейс ітератора
interface IIterator<T> {
  ...
}

// Клас конкретного ітератора
class ConcreteIterator1<T> implements IIterator<T> {
  ...
}

// Клас, що реалізує конкретний контейнер.
// Контейнером служить масив типу ArrayList<T>
class ConcreteAggregate1<T> implements IAggregate<T> {
  // Внутрішні поля класу
  private ArrayList<T> array; // масив елементів

  // Конструктор
  public ConcreteAggregate1(T[] _array) {
    array = new ArrayList<T>();
    for (int i=0; i<_array.length; i++)
      array.add(_array[i]);
  }

  // Методи оперування контейнером
  // Додати елемент в кінець контейнера
  public void Append(T item) {
    array.add(item);
  }

  // Видалити елемент з контейнера
  public void Remove(int index) {
    array.remove(index);
  }

  public void Print(String text) {
    System.out.println(text);
    for (int i=0; i<array.size(); i++)
      System.out.print(array.get(i) + " ");
    System.out.println();
  }

  // Методи інтерфейсу IAggregate<T>
  // Повернути ітератор
  public IIterator<T> CreateIterator() {
    return new ConcreteIterator1<T>(this);
  }

  // Повернути довжину контейнера
  public int Count() {
    return array.size();
  }

  // Повернути поточний елемент
  public T GetItem(int index) {
    if ((index>=0)&&(index<array.size()))
      return array.get(index);
    else
      throw new ArrayIndexOutOfBoundsException("Error. Bad index.");
  }
}

// Клієнтський клас
public class TestIterator {
  public static void main(String[] args) {
  }
}

 

2.6. Клас клієнта

Клієнтський код наведений у функції main(). У клієнтському коді тестується робота паттерну Iterator.

import java.util.ArrayList;

// Паттерн Iterator. Реалізація на Java
// Інтерфейс контейнера (агрегату)
interface IAggregate<T> {
  ...
}

// Інтерфейс ітератора
interface IIterator<T> {
  ...
}

// Клас конкретного ітератора
class ConcreteIterator1<T> implements IIterator<T> {
  ...
}

// Клас, що реалізує конкретний контейнер.
class ConcreteAggregate1<T> implements IAggregate<T> {
  ...
}

public class TestIterator {
  public static void main(String[] args) {
    // Тестування паттерну Iterator. Код клієнта
    // 1. Оголосити масив даних
    Double[] AD = { 1.1, 2.3, 8.5, -4.9 };

    // 2. Створити контейнер на основі масиву AD
    ConcreteAggregate1<Double> ag1 = new ConcreteAggregate1<Double>(AD);
    ag1.Print("ag1");

    // 3. Створити ітератор для контейнеру ag1
    ConcreteIterator1<Double> it1 =
      (ConcreteIterator1<Double>) ag1.CreateIterator();

    // 4. Продемонструвати роботу ітератора
    System.out.println("-------------------");
    System.out.println("Access using iterator.");

    it1.First();
    while (!it1.IsDone()) {
      System.out.print(it1.CurrentItem()+" ");
      it1.Next();
    }
  }
}

Результат виконання програми

ag1
1.1 2.3 8.5 -4.9
-------------------
Access using iterator.
1.1 2.3 8.5 -4.9

 

3. Додавання нового ітератора. Послідовність кроків

Щоб додати новий конкретний ітератор (наприклад, зворотній ітератор), потрібно виконати наступні кроки.

  1. Створити клас, що реалізує інтерфейс IIterator<T>.
  2. Оголосити внутрішні дані в класі, специфічні для даного ітератора.
  3. Оголосити реалізацію методів, оголошених в інтерфейсі IIterator<T>.
  4. Реалізувати додаткові методи.

Приблизний код для класу MyNewIterator<T> може бути, наприклад, таким.

class MyNewIterator<T> implements IIterator<T>
{
  // 1. Внутрішні дані
  // ...

  // 2. Додаткові методи класу
  // ...

  // 3. Методи, що оголошені в інтерфейсі IIterator<T>
  public void First() {
    ...
  }

  public void Next() {
    ...
  }

  public boolean IsDone() {
    ...
  }

  public T CurrentItem() {
    ...
  }

  // 4. Додаткові методи інтерфейсу, специфічні для даного ітератора
  // ...
}

 

4. Додавання нового контейнера. Послідовність кроків

Щоб додати новий конкретний контейнер, потрібно виконати такі кроки.

  1. Створити клас, що реалізує інтерфейс IAggregate<T>.
  2. Оголосити внутрішні дані класу-контейнера. Це можуть бути масив, список, дерево, множина тощо.
  3. Оголосити методи, що забезпечують основний функціонал контейнера (додати новий елемент, видалити елемент тощо).
  4. Реалізувати методи, оголошені в інтерфейсі IAggregate<T>.
  5. Реалізувати додаткові методи, що забезпечують функціонал контейнера.

Наприклад, клас з іменем MyNewAggregate<T> може мати наступну реалізацію.

class MyNewAggregate<T> implements IAggregate<T> {
  // 1. Внутрішні поля класу
  // ...

  // 2. Конструктори та методи оперування контейнером
  // ...

  // 3. Методи оперування контейнером
  // ...

  // 4. Методи інтерфейсу IAggregate<T>
  // ...
  public IIterator<T> CreateIterator() {
    return new MyNewAggregate<T>(this);
  }

  public int Count() { ... }
  public T GetItem(int index) { ... }

  // 5. Додаткові спеціалізовані методи
  // ...
}

 


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