Kihagyás

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?

  1. Egységes API: akármilyen adatszerkezet van mögötte, mindig ugyanaz a két metódus elég: hasNext() és next().
  2. Egyszeri felelősség (SRP): a gyűjteménynek a tárolás a dolga, az iterátornak a bejárás. A BookCollection tárol, a BookIterator lépked.
  3. 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> és java.lang.Iterable<T> interfészek a nyelv részei. A saját gyűjteményünk a for-each ciklussal 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.
 */