Паттерн Iterator. Реалізація на Java з підтримкою поліморфного контейнера та поліморфного ітератора
Перед вивченням даної теми рекомендується ознайомитись з наступною темою:
Зміст
- 1. Структурна схема паттерну Iterator з прив’язкою до коду Java
- 2. Програмна реалізація
- 3. Додавання нового ітератора. Послідовність кроків
- 4. Додавання нового контейнера. Послідовність кроків
- Зв’язані теми
Пошук на інших ресурсах:
1. Структурна схема паттерну Iterator з прив’язкою до коду Java
На рисунку зображено схему паттерну Iterator з підтримкою поліморфного контейнера та поліморфного ітератора.
Рисунок. Схема паттерну 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. Додавання нового ітератора. Послідовність кроків
Щоб додати новий конкретний ітератор (наприклад, зворотній ітератор), потрібно виконати наступні кроки.
- Створити клас, що реалізує інтерфейс IIterator<T>.
- Оголосити внутрішні дані в класі, специфічні для даного ітератора.
- Оголосити реалізацію методів, оголошених в інтерфейсі IIterator<T>.
- Реалізувати додаткові методи.
Приблизний код для класу 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. Додавання нового контейнера. Послідовність кроків
Щоб додати новий конкретний контейнер, потрібно виконати такі кроки.
- Створити клас, що реалізує інтерфейс IAggregate<T>.
- Оголосити внутрішні дані класу-контейнера. Це можуть бути масив, список, дерево, множина тощо.
- Оголосити методи, що забезпечують основний функціонал контейнера (додати новий елемент, видалити елемент тощо).
- Реалізувати методи, оголошені в інтерфейсі IAggregate<T>.
- Реалізувати додаткові методи, що забезпечують функціонал контейнера.
Наприклад, клас з іменем 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. Додаткові спеціалізовані методи // ... }
⇑
Зв’язані теми
- Загальні відомості. Способи реалізації. Структурна схема. Приклад на C++
- Паттерн Iterator. Особливості реалізації на C++ для поліморфного контейнера та поліморфного ітератора
- Зовнішній та внутрішній ітератор. Реалізація на C++
- Паттерн Iterator. Реалізація на C#
⇑