Мені було цікаво, що саме те, що робить Ітератор особливим у порівнянні з іншими подібними конструкціями, і що змусило Банку чотирьох перерахувати його як модель дизайну.
Ітератор заснований на поліморфізмі (ієрархії колекцій із загальним інтерфейсом) та розділенні проблем (повторення над колекціями має бути незалежним від способу структурування даних).
Але що робити, якщо замінити ієрархію колекцій, наприклад, ієрархією математичних об'єктів (ціле число, флоат, комплекс, матриця тощо) та ітератором класом, що представляє деякі пов'язані з ними операції, наприклад функції живлення. Діаграма класів була б однаковою.
Напевно, ми можемо знайти ще багато подібних прикладів, таких як Writer, Painter, Encoder і, мабуть, кращі, які працюють однаково. Однак я ніколи не чув, щоб жодне з них називалося «Шаблон дизайну».
Отже, що робить Ітератор особливим?
Це факт, що він складніший, оскільки для збереження поточної позиції в колекції він потребує змінного стану? Але тоді стан, що змінюється, зазвичай не вважається бажаним.
Щоб уточнити свою думку, дозвольте навести більш детальний приклад.
Ось наша проблема дизайну:
Скажімо, у нас є ієрархія класів і операція, визначена на об'єктах цих класів. Інтерфейс цієї операції однаковий для кожного класу, але реалізації можуть бути абсолютно різними. Також передбачається, що має сенс застосовувати операцію кілька разів на одному об’єкті, скажімо, з різними параметрами.
Ось розумне рішення нашої дизайнерської задачі (практично узагальнення схеми ітератора):
Для розмежування проблем, реалізації операції не слід додавати як функції до початкової ієрархії класів (об'єкти операнда). Оскільки ми хочемо застосувати операцію кілька разів на одному операнді, вона повинна бути представлена об'єктом, що містить посилання на операнда, а не лише функцією. Тому об'єкт операнда повинен забезпечувати функцію, яка повертає об'єкт, що представляє операцію. Цей об'єкт забезпечує функцію, яка виконує фактичну операцію.
Приклад:
Є базовий клас або інтерфейс MathObject
(дурне ім'я, я знаю, можливо, хтось має кращу ідею.) З похідними класами MyInteger
і MyMatrix
. Для кожної MathObject
операції Power
слід визначити, що дозволяє обчислити квадрат, куб тощо. Тож ми могли написати (на Java):
MathObject i = new MyInteger(5);
Power powerOfFive = i.getPower();
MyInteger square = powerOfFive.calculate(2); // should return 25
MyInteger cube = powerOfFive.calculate(3); // should return 125