Ітератор
Суть патерна
Ітератор — це поведінковий патерн проектування, що дає змогу послідовно обходити елементи складових об’єктів, не розкриваючи їхньої внутрішньої організації.
Проблема
Колекції — це найпоширеніша структура даних, яку ви можете зустріти в програмуванні. Це набір об’єктів, зібраний в одну купу за якимись критеріями.
Більшість колекцій виглядають як звичайний список елементів. Але є й екзотичні колекції, побудовані на основі дерев, графів та інших складних структур даних.
Незважаючи на те, яким чином структуровано колекцію, користувач повинен мати можливість послідовно обходити її елементи, щоб виконувати з ними певні дії.
У який же спосіб слід переміщатися складною структурою даних? Наприклад, сьогодні може бути достатнім обхід дерева в глибину, але завтра виникне необхідність переміщуватися деревом по ширині. А на наступному тижні, хай йому грець, знадобиться можливість обходу колекції у випадковому порядку.
Додаючи все нові алгоритми до коду колекції, ви потроху розмиваєте її основну задачу, що полягає в ефективному зберіганні даних. Деякі алгоритми можуть бути аж занадто «заточені» під певну програму, а тому виглядатимуть неприродно в загальному класі колекції.
Рішення
Ідея патерна Ітератор полягає в тому, щоб винести поведінку обходу колекції з самої колекції в окремий об’єкт.
Об’єкт-ітератор відстежуватиме стан обходу, поточну позицію в колекції та кількість елементів, які ще залишилося обійти. Одну і ту саму колекцію зможуть одночасно обходити різні ітератори, а сама колекція навіть не знатиме про це.
До того ж, якщо вам потрібно буде додати новий спосіб обходу, ви зможете створите окремий клас ітератора, не змінюючи існуючого коду колекції.
Аналогія з життя
Ви плануєте полетіти до Риму та обійти всі визначні пам’ятки за кілька днів. Але по приїзді ви можете довго блукати вузькими вуличками, намагаючись знайти один тільки Колізей.
Якщо у вас обмежений бюджет, ви можете скористатися віртуальним гідом, встановленим у смартфоні, який дозволить відфільтрувати тільки цікаві вам об’єкти. А можете плюнути на все та найняти місцевого гіда, який хоч і обійдеться в копієчку, але знає все місто, як свої п’ять пальців, і зможе «занурити» вас в усі міські легенди.
Таким чином, Рим виступає колекцією пам’яток, а ваш мозок, навігатор чи гід — ітератором колекції. Ви як клієнтський код можете вибрати одного з ітераторів, відштовхуючись від вирішуваного завдання та доступних ресурсів.
Структура
-
Ітератор описує інтерфейс для доступу та обходу елементів колекцій.
-
Конкретний ітератор реалізує алгоритм обходу якоїсь конкретної колекції. Об’єкт ітератора повинен сам відстежувати поточну позицію при обході колекції, щоб окремі ітератори могли обходити одну і ту саму колекцію незалежно.
-
Колекція описує інтерфейс отримання ітератора з колекції. Як ми вже говорили, колекції не завжди є списком. Це може бути і база даних, і віддалене API, і навіть дерево Компонувальника. Тому сама колекція може створювати ітератори, оскільки вона знає, які саме ітератори здатні з нею працювати.
-
Конкретна колекція повертає новий екземпляр певного конкретного ітератора, зв’язавши його з поточним об’єктом колекції. Зверніть увагу на те, що сигнатура методу повертає інтерфейс ітератора. Це дозволяє клієнтові не залежати від конкретних класів ітераторів.
-
Клієнт працює з усіма об’єктами через інтерфейси колекції та ітератора. Через це клієнтський код не залежить від конкретних класів, що дозволяє застосовувати різні ітератори, не змінюючи існуючого коду програми.
В загальному випадку клієнти не створюють об’єкти ітераторів, а отримують їх з колекцій. Тим не менше, якщо клієнтові потрібний спеціальний ітератор, він завжди може створити його самостійно.
Псевдокод
У цьому прикладі патерн Ітератор використовується для реалізації обходу нестандартної колекції, яка інкапсулює доступ до соціального графа Facebook. Колекція надає декілька ітераторів, які можуть обходити профілі людей різними способами.
Зокрема, ітератор друзів перебирає всіх друзів профілю, а ітератор колег фільтрує друзів згідно їхньої приналежності до компанії профілю. Всі ітератори реалізують спільний інтерфейс, який дає змогу клієнтам працювати з профілями, не заглиблюючись у деталі роботи з соціальною мережею (наприклад, авторизацію, надсилання REST запитів та інше).
Крім того, Ітератор позбавляє код від прив’язки до конкретних класів колекцій. Це дозволяє додати підтримку іншого виду колекцій (наприклад, LinkedIn), не змінюючи клієнтський код, який працює з ітераторами та колекціями.
Застосування
Якщо у вас є складна структура даних, і ви хочете приховати від клієнта деталі її реалізації (з питань складності або безпеки).
Ітератор надає клієнтові лише кілька простих методів перебору елементів колекції. Це не тільки спрощує доступ до колекції, але й захищає її від необережних або злочинних дій.
Якщо вам потрібно мати кілька варіантів обходу однієї і тієї самої структури даних.
Нетривіальні алгоритми обходу структури даних можуть мати досить об’ємний код. Цей код буде захаращувати все навкруги — чи то самий клас колекції, чи частина бізнес-логіки програми. Застосувавши ітератор, ви можете виділити код обходу структури даних в окремий клас, спростивши підтримку решти коду.
Якщо вам хочеться мати єдиний інтерфейс обходу різних структур даних.
Ітератор дозволяє винести реалізації різних варіантів обходу в підкласи. Це дозволить легко взаємозаміняти об’єкти ітераторів в залежності від того, з якою структурою даних доводиться працювати.
Кроки реалізації
-
Створіть загальний інтерфейс ітераторів. Обов’язковий мінімум — це операція отримання наступного елемента. Але для зручності можна передбачити й інше. Наприклад, методи отримання попереднього елементу, поточної позиції, перевірки закінчення обходу тощо.
-
Створіть інтерфейс колекції та опишіть у ньому метод отримання ітератора. Важливо, щоб сигнатура методу повертала загальний інтерфейс ітераторів, а не один з конкретних ітераторів.
-
Створіть класи конкретних ітераторів для тих колекцій, які потрібно обходити за допомогою патерна. Ітератор повинен бути прив’язаний тільки до одного об’єкта колекції. Зазвичай цей зв’язок встановлюється через конструктор.
-
Реалізуйте методи отримання ітератора в конкретних класах колекцій. Вони повинні створювати новий ітератор того класу, який здатен працювати з даним типом колекції. Колекція повинна передавати посилання на власний об’єкт до конструктора ітератора.
-
У клієнтському коді та в класах колекцій не повинно залишитися коду обходу елементів. Клієнт повинен отримувати новий ітератор з об’єкта колекції кожного разу, коли йому потрібно перебрати її елементи.
Переваги та недоліки
- Спрощує класи зберігання даних.
- Дозволяє реалізувати різні способи обходу структури даних.
- Дозволяє одночасно переміщуватися структурою даних у різних напрямках.
- Невиправданий, якщо можна обійтися простим циклом.
Відносини з іншими патернами
-
Ви можете обходити дерево Компонувальника, використовуючи Ітератор.
-
Фабричний метод можна використовувати разом з Ітератором, щоб підкласи колекцій могли створювати необхідні їм ітератори.
-
Знімок можна використовувати разом з Ітератором, щоб зберегти поточний стан обходу структури даних та повернутися до нього в майбутньому, якщо буде потрібно.
-
Відвідувач можна використовувати спільно з Ітератором. Ітератор відповідатиме за обхід структури даних, а Відвідувач — за виконання дій над кожним її компонентом.