Iterator (Bejáró)
Kategória: Viselkedési minta (Behavioral Pattern)
Lényege és célja
Az Iterator minta egy egységes módot ad arra, hogy egy gyűjtemény (collection / aggregate) elemein sorban végighaladjunk, anélkül, hogy a kliensnek tudnia kéne, hogyan van belül a gyűjtemény implementálva (tömb, láncolt lista, fa, hash-tábla stb.).
A bejárás állapotát (hol tartunk éppen) nem a gyűjtemény, hanem maga az iterátor objektum tartja számon, így egyszerre több, független iterátor is létezhet ugyanazon az adathalmazon.
Miért külön osztály a bejárás?
- Egységes API: akármilyen adatszerkezet van mögötte, mindig ugyanaz a két metódus elég:
hasNext()ésnext(). - Egyszeri felelősség (SRP): a gyűjteménynek a tárolás a dolga, az iterátornak a bejárás. A
BookCollectiontárol, aBookIteratorlépked. - Több párhuzamos bejárás: ha a bejárás állapotát a gyűjtemény tartaná, csak egyetlen "kurzor" lehetne benne; külön iterátor objektumokkal viszont akárhány lehet.
Mikor használjuk?
- Amikor saját gyűjteményt írunk, és el akarjuk rejteni annak belső adatszerkezetét a kliens elől.
- Amikor egy adathalmazon többféle bejárási sorrendet (előre/hátra, szűrt, mély/széles) is támogatni szeretnénk – minden sorrendhez külön iterátor osztály.
- Amikor egységes módon akarunk heterogén gyűjteményeken (lista, fa, gráf) ugyanazzal a kód-mintázattal végigmenni.
Java-specifikus megjegyzés: ezt a mintát annyira sokat használjuk, hogy Java-ban a
java.util.Iterator<T>ésjava.lang.Iterable<T>interfészek a nyelv részei. A saját gyűjteményünk afor-eachciklussal automatikusan használható lesz, ha ezeket implementáljuk – nem szoktunk teljesen saját interfészeket írni rá.
Mermaid Diagram
A diagramon a két klasszikus interfész – Iterable (a gyűjtemény oldala) és Iterator (a bejárás oldala) – kapcsolata látszik. A gyűjtemény legyárt egy iterátort, ami a saját, független állapotában tartja nyilván, hol tart.
classDiagram
class Iterable {
<<interface>>
+createIterator() Iterator
}
class Iterator {
<<interface>>
+hasNext() boolean
+next() Book
}
class BookCollection {
-items: List~Book~
+addBook(b: Book) void
+createIterator() Iterator
}
class BookIterator {
-items: List~Book~
-currentIndex: int
+hasNext() boolean
+next() Book
}
class Book {
-title: String
}
class Client
BookCollection ..|> Iterable : "implementálja"
BookIterator ..|> Iterator : "implementálja"
BookCollection o--> Book : "tárolja"
BookCollection ..> BookIterator : "legyárt"
BookIterator --> Book : "visszaad"
Client --> BookCollection : "használja"
Client --> Iterator : "csak az interfészt" Forráskód
package behavioral.iterator;
import java.util.ArrayList;
import java.util.List;
class Book {
private final String title;
Book(String title) {
this.title = title;
}
@Override
public String toString() {
return "Book{" +
"title='" + title + '\'' +
'}';
}
}
interface Iterator {
boolean hasNext();
Book next();
}
interface Iterable {
Iterator createIterator();
}
class BookCollection implements Iterable {
private final List<Book> items = new ArrayList<>();
public void addBook(Book book) {
items.add(book);
}
@Override
public Iterator createIterator() {
return new BookIterator(items);
}
}
class BookIterator implements Iterator {
private final List<Book> items;
int currentIndex = 0;
public BookIterator(List<Book> items) {
this.items = items;
}
@Override
public boolean hasNext() {
return items.size() > currentIndex;
}
@Override
public Book next() {
var item = items.get(currentIndex);
currentIndex++;
return item;
}
}
class Main {
static void main() {
var bookCollection = new BookCollection();
bookCollection.addBook(new Book("book 1"));
bookCollection.addBook(new Book("book 2"));
bookCollection.addBook(new Book("book 3"));
bookCollection.addBook(new Book("book 4"));
var bookIterator = bookCollection.createIterator();
while (bookIterator.hasNext()) {
System.out.println(bookIterator.next());
}
}
}
/*
Javában ezt a mintát annyira sokat használjuk, hogy beépítették magába a nyelvbe.
A valóságban szinte sosem írunk saját Iterator és Aggregate interfészt,
hanem a java beépített java.util.Iterator<T> és java.lang.Iterable<T> interfészeit implementáljuk,
így a gyűjteményünket azonnal lehet használni a standard for-each ciklussal is.
*/