Iterator

Wzorce projektowe

  |   6 min czytania

Zastosowanie

Iterator (ang. Iterator) (wzorzec behawioralny) zapewnia sekwencyjny dostęp do grupy obiektów zawierających się w innym obiekcie (kontenerze) oraz umożliwia przechodzenie po tej samej strukturze danych na wiele sposobów. Kontener Container dostarcza zgodny z typem przechowywanych danych obiekt Iterator, który zapewnia dostęp do elementów kolekcji bez ujawniania jej reprezentacji. Niezależnie od typu kolekcji, jej elementy mogą być przetwarzane sekwencyjnie przy zachowaniu własności danej kolekcji. Konstrukcja pętli for-each w sposób niejawny wywołuje Iterator, co sprawia że wzorzec Iterator jest standardowym elementem wielu języków programowania.

Ograniczenia

Ze względu na większą złożoność implementacji Iteratora oraz mniejszą czytelność kodu w stosunku do zastosowania pętli for-each zaleca się wykorzystanie konstrukcji for-each o ile to możliwe. Ponadto Iterator jest podatny na sytuację odwołania się do kolejnego elementu, który nie istnieje.

Użycie

Wzorzec Iterator wykorzystywany jest wszędzie tam gdzie zachodzi potrzeba uniwersalnego dostępu do elementów kolekcji z pominięciem różnic w ich implementacji. Stosowany jest także, gdy użycie standardowej pętli for-each jest niewystarczające ze względu na np.: brak iteracji w obu kierunkach czy też usuwania elementów z kolekcji w trakcie iteracji.

Implementacja

Klasa kolekcji elementów (o danej strukturze) implementuje interfejs Container i dostarcza właściwą dla siebie instancję Iteratora oraz jego implementacje w klasie wewnętrznej.

Iterator diagram

Poniższy listing przedstawia implementacje wzorca Iterator dla kolekcji typu List.

public class Container1 implements Container {

    //collection of some type in some structure
    private List<E> collection;
    private int position = 0;

    public Container1(List<E> collection) {
        this.collection = collection;
    }

    //some specific operations for Container like add, remove, get etc.

    @Override
    public Iterator getIterator() {
        return new Iterator1();
    }

    private class Iterator1 implements Iterator {

        @Override
        public boolean hasNext() {
            if(position >= collection.size() || collection.get(position) == null)
                return false;
            else
                return true;
        }

        @Override
        public boolean hasPrevious() {
            if(position < 0 || collection.get(position) == null)
                return false;
            else
                return true;
        }

        @Override
        public E next() {
            E item = collection.get(position);
            position++;
            return item;
        }

        @Override
        public E previous() {
            E item = collection.get(position);
            position--;
            return item;
        }

        @Override
        public void remove() {
            if(position >= 0 && collection.get(position) != null)
                collection.remove(position);
        }
    }
}

//implement other Iterators in similar way depends on structure

interface Container {

    Iterator getIterator();
}

interface Iterator<E> {

    boolean hasNext();
    boolean hasPrevious();
    E next();
    E previous();
    void remove();
    //could be also some action methods
}

W celu wykonania operacji na elementach wybranej kolekcji, klient wywołuje jej Iterator.

//setup container
List<String> persons = new ArrayList<>();
persons.add("John Black");
persons.add("Joe Smith")
persons.add("Frank Jones");
persons.add("Tom Shaw");
persons.add("Patrick Jones");
Container container = new Container1(persons);

//iterate and do action for every elements of container
Iterator<String> iterator = container.getIterator();
while(iterator.hasNext()) {
    //do some action on the object
    String person = iterator.next();
    if(person.contains("Jones"))
        iterator.remove(); //delete person and iterate forward
    else {
        //do some action
    }
}

Przykład

Aplikacja FootballScore prezentuje zestaw szczegółowych danych nt rozgrywek piłkarskich (w tym tabele oraz terminarze) w europejskich ligach oraz pucharach. W celu przedstawienia pełnej kolekcji informacji otrzymanych z serwera oraz umożliwieniu zastosowania filtrów, programista zdecydował o użyciu wzorca Iterator. Ze względu na zastosowane typy kolekcji (np.: ArrayList) pochodzące ze standardowego pakietu Java, programista unika potrzeby tworzenia własnej implementacji Iterator poprzez wykorzystanie tej dostarczonej przez wybraną kolekcję.

//get data from server
//get list of teams from choosen league with points and scores
List<Team> teams = new ArrayList<>();
teams.add(new Team("AC Milan", 1, 50, 86, 25));
teams.add(new Team("Juventus FC", 2, 47, 72, 23));
teams.add(new Team("AS Roma", 3, 43, 50, 12));
teams.add(new Team("SSC Napoli", 4, 40, 95, 45));
teams.add(new Team("Inter Milan", 5, 38, 63, 39));
//etc

//show table of the league
for(Team team : teams) {
    //add to the table
}

//user choose filter to show data only for teams with min 60 goals scored
//for each loop is not enough because of removing elements
Iterator<Team> iterator = teams.iterator();
for(iterator; iterator.hasNext();) {
    Team team = iterator.next();
    if(team.getGainedGoals() < 60) {
        //those teams are no longer needed for user processes
        iterator.remove(); //AS Roma and Inter Milan removed
    }
    else {
        //add to filtered table
    }
}

Biblioteki

Wszystkie implementacje Enumeration, Iterator, Spliterator standardowego pakietu Java, a zatem wszystkie kolekcje implementujące interfejs Iterable realizują zadanie wzorca Iterator. Ze względu na ich generyczność oraz dostępność, mogą być z powodzeniem zastosowane w wielu przypadkach bez konieczności tworzenia własnych interfejsów.