Home > GOF Pattern, java > GOF Patterns: Iterator

GOF Patterns: Iterator

29 novembre 2011

Translate in English with Google Translate
In questo articolo tratterò il pattern Iterator anche detto Cursor

Motivazione

Si tratta di un pattern comportamentale basato su oggetti e viene utilizzato quando, dato un aggregato di oggetti, si vuole accedere ai suoi elementi senza dover esporre la sua struttura. L’obiettivo di questo pattern è quello di disaccoppiare l’utilizzatore e l’implementatore dell’aggregazione di dati, tramite un oggetto intermedio che esponga sempre gli stessi metodi indipendentemente dall’aggregato di dati.

E’ costituito da 3 soggetti: l’Utilizzatore dei dati, l’Iteratore che intermedia i dati e l’Aggregatore che detiene i dati secondo una propria logica.

Partecipanti e Struttura
Questo pattern è composto dai seguenti partecipanti:

  • Iterator: colui che espone i metodi di accesso alla struttura dati
  • ConcreteIterator: implementa l’Iteratore e tiene il puntatore alla struttura dati
  • Aggregator: definisce l’interfaccia per creare un oggetto di tipo Iteratore
  • ConcreteAggregator: implementa l’interfaccia di creazione di un oggetto Iteratore

Conseguenze

Tale pattern presenta i seguenti vantaggi/svantaggi:

  • unica interfaccia di accesso: l’accesso ai dati avviene tramite l’Iterator che espone un’unica interfaccia e nasconde le diverse implementazioni degli Aggregator
  • diversi iteratori di accesso: l’Aggregator può essere attraversato tramite diversi Iterator in cui ogni Iterator nasconde un algoritmo diverso

Implementazione

In Java un Aggregatore si ottiene tramite un array, una lista, un set, un’hash, una coda ecc oppure tramite un oggetto completamente custom. Ogni oggetto aggregatore dispone di propri metodi di accesso e manipolazione dei dati.
L’Iteratore è in grado di interagire con l’Aggregatore in quanto conosce i suoi dettagli implementativi ed espone all’esterno sempre la stessa interfaccia di interrogazione.

Nelle librerie java questo pattern è già presente attraverso le interfacce:

  1. java.util.Iterator ( Iteratore ) : nasconde le strutture dati esponendo sempre gli stessi metodi di interrogazione.
  2. java.lang.Iterable ( Aggregatore ): dichiara il supporto alla funzionalità di iterazione tale da consentire la creazione di un oggetto Iteratore

La classe java.util.ArrayList implementa indirettamente, passando per List e Collection, l’interfaccia java.lang.Iterable e la classe annidata ArrayList.Itr implementa l’interfaccia java.util.Iterator, in modo da realizzare l’Aggregatore e l’Iteratore.

Vediamo prima il Class Diagram UML e successivamento degli stralci delle classi del JDK:

Iterator Pattern Java

Iterator Pattern Java

Prima di tutto vediamo l’interfaccia dell’Aggregatore che definisce l’Iteratore.

package java.lang;
import java.util.Iterator;
public interface Iterable {
    Iterator iterator();
}

Adesso vediamo come nel Framework delle Collection viene ereditata a monte la disponibilità di avere un Iteratore per ogni Aggregatore.

package java.util;
public interface Collection extends Iterable {
    Iterator iterator();
}

Nel nostro caso esaminiamo una List, che eredita dalle Collection:

package java.util;
public interface List extends Collection {
    Iterator iterator();
}

L’interfaccia dell’Iteratore espone i metodi di controllo hasNext(), passo next e cancellazione remove():

package java.util;
public interface Iterator {
    boolean hasNext();
    E next();
    void remove();
}

Finalmente siamo arrivati alla classe ArrayList che implementa List, pertanto è un Aggregatore ma al suo interno definisce una classe annidata di tipo Iterator che implementa i metodi puntatori all’Aggregatore.
La classe annidata consente di ottimizzare l’utilizzo della memoria, la creazione di oggetti e l’accesso ai dati.

package java.util;
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable {
    public Iterator iterator() {
        return new Itr();
    }
    private class Itr implements Iterator {
        int cursor;       // index of next element to return

        public boolean hasNext() {
            return cursor != size;
        }

        public E next() {
            int i = cursor;
            Object[] elementData = ArrayList.this.elementData;
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
        //...
        }
    }
}

Esempio
Come esempio ho creato un Aggregatore che consente di aggiungere nuovi elementi, inoltre consente di essere wrappato da un Iteratore che consente di accedere in modo sequenziale agli elementi inseriti in precedenza.
Vediamo come si presenta il Class Diagram UML in base all’esempio:

Iterator Pattern esempio

Iterator Pattern esempio

Creiamo l’interfaccia dell’Aggregatore che definisce il metodo di accesso all’Iteratore

package patterns.iterator;

public interface Aggregate {

    public MyIterator createIterator();
    public void add(String item);

}

Adesso possiamo creare la classe Aggregatore che implementa la struttura dati. In questo caso per semplicità non faccio altro che wrappare un ArrayList.

package patterns.iterator;

import java.util.ArrayList;

public class ConcreteAggregate implements Aggregate {

    private ArrayList arrayList;

    public ConcreteAggregate() {
        arrayList = new ArrayList();
    }

    @Override
    public MyIterator createIterator() {
        return new ConcreteIterator( arrayList );
    }

    @Override
    public void add(String item) {
        arrayList.add( item );
    }

}

Definiamo l’interfaccia dell’Iteratore che prevede i metodi di interrogazione hasNext() ed il puntatore all’elemento successivo next().

package patterns.iterator;

public interface MyIterator {

    public boolean hasNext();
    public Object next();

}

Implementiamo l’Iteratore, in particolare i metodi di interrogazione hasNext() ed il puntatore all’elemento successivo next():

package patterns.iterator;

import java.util.ArrayList;

public class ConcreteIterator implements MyIterator {

    private ArrayList arrayList;
    private int current = -1;

    public ConcreteIterator( ArrayList arrayList ) {
        this.arrayList = arrayList;
    }

    @Override
    public String next() {
        current++;
        return arrayList.get(current);
    }

    @Override
    public boolean hasNext() {
        return (current+1) < arrayList.size();
    }

}

Il Client funge da invocatore, crea un aggregato di dati e poi lo scorre tramite l’Iteratore:

package patterns.iterator;

public class Client {

    public static void main(String[] args) {
        Aggregate aggregate = new ConcreteAggregate();
        aggregate.add("item1");
        aggregate.add("item2");

        MyIterator iterator = aggregate.createIterator();
        while ( iterator.hasNext() ) {
            System.out.println( iterator.next() );
        }
    }

}

Il risultato è il seguente:

$JAVA_HOME/bin/java patterns.iterator.Client
item1
item2
Categorie:GOF Pattern, java
%d blogger cliccano Mi Piace per questo: