Software-Entwurfsmuster

Compound-Iterator

Ein Compound-Iterator durchläuft quasi das kartesische Produkt zweier iterierbarer Folgen, also die Folge aller Paare aus den Elementen dieser Folgen. Normalerweise benötigt man zwei ineinander geschachtelte For-Schleifen, um die Elemente dieses kartesischen Produkts zu durchlaufen.

Ein Compound-Iterator hat dagegen den Vorteil, dass er, einmal geschrieben, nur eine For-Schleife benötigt und dass er überall einsetzbar ist, wo ein Iterator stehen kann, zum Beispiel als Basisiterator eines Filteriterators.

Problem

Es sollen alle Positionen eines 8×8-Spielbretts durchlaufen werden. Ein Iterator hierfür verwendet einen Basisiterator für die Zeilen und einen Subiterator für die Spalten, und er setzt dann aus dem jeweiligen Zeilenindex i und dem Spaltenindex j eine Position (i,j) zusammen.

Die Klasse Position kapselt ein solches Paar von Integer-Werten und stellt entsprechende Methoden für Rechenoperationen zur Verfügung.

 

public class Position
{
    // Attribute
    public int z, s;

    // Konstruktor
    public Position(int z, int s)
    {
        this.z=z;
        this.s=s;
    }
    
    // Methoden
    public Position add(Position other)
    {
        return new Position(this.z+other.z, this.s+other.s);
    }

    //  ...

    public String toString()
    {
        return "("+z+","+s+")";
    }

}

Compound-Iterator: Idee

Gesucht ist nun ein Iterator, der alle Positionen des Spielbretts durchläuft. Ein solcher Iterator lässt sich als Compound-Iterator realisieren. Ein Compound-Iterator benutzt einen Basisiterator (im Beispiel der Iterator für die Zeilen) und einen Subiterator (im Beispiel der Iterator für die Spalten). Ferner wird eine Funktion composeItems benötigt, um aus den beiden Cursor-Objekten der beiden Iteratoren ein Objekt zu machen, das mit next() zurückgegeben wird (im Beispiel wird ein Position-Objekt aus dem Zeilenindex und dem Spaltenindex erzeugt).

Basisiterator und Subiterator

Als Basisiterator und als Subiterator wird ein Generator verwendet, der nacheinander die Zahlen von 0 bis n-1 liefert.

 

public class IntIterator implements Iterator<Integer>
{
    private int i, n;

    public IntIterator(int n)
    {
        this.n=n;
        i=0;
    }

    @Override
    public boolean hasNext()
    {
        return i<n;
    }

    @Override
    public Integer next()
    {
        return i++;
    }

}

 

Behavior AllPositions

Der Basisiterator, der Subiterator und die Funktion composeItems werden in einem sogenannten Behavior festgelegt, mit dem der Compound-Iterator arbeitet. Das folgende Behavior AllPositions ergibt einen Compound-Iterator, der alle Positionen eines rows×cols-Spielbretts durchläuft. Im Konstruktor werden die Werte für rows und für cols übergeben. Es folgen dann die drei Methoden, die in der abstrakten Basisklasse CompoundIteratorBehavior festgelegt sind.

In der Methode baseIterator wird der Basisiterator spezifiziert, der die Zahlen von 0 bis rows-1 durchläuft, und in der Methode subIterator der Subiterator, der die Zahlen von 0 bis cols-1 durchläuft. Im Allgemeinen ist es möglich, dass der Subiterator vom Cursor-Objekt x0 des Basisiterators abhängig ist, deswegen der Parameter x0, dies wird aber hier nicht benutzt. Die Methode composeItems setzt aus dem Cursor-Objekt x0 des Basisiterators und dem Cursor-Objekt x1 des Subiterators eine Position (x0,x1) zusammen.

Die erwähnte Factory-Methode, die den fertigen Iterator zurückgibt, befindet sich in der Basisklasse CompoundIteratorBehavior.

 

public class AllPositions extends CompoundIteratorBehavior<Integer, Integer, Position>
{
    private int rows, cols;

    public AllPositions(int rows, int cols)
    {
        this.rows=rows;
        this.cols=cols;
    }

    @Override
    // the base iterator
    public Iterator<Integer> baseIterator()
    {
        return new IntIterator(rows);
    }

    @Override
    // the subiterator
    public Iterator<Integer> subIterator(Integer x0)
    {
        return new IntIterator(cols);
    }

    @Override
    // specifies the object composed of the two cursor objects x0 and x1
    public Position composeItems(Integer x0, Integer x1)
    {
        return new Position(x0, x1);
    }

}

Der Compound-Iterator ist nach dem Bridge-Entwurfsmuster gebildet. Er verwendet ein Behavior, das den Basisiterator, den Subiterator und die Funktion composeItems bereitstellt (Bild 1). Das Behavior enthält auch gleich noch eine Factory-Methode, um den so gebildeten Iterator zu erzeugen.

 

Bild 1: Klassendiagramm eines Compound-Iterators mit dem konkreten Behavior AllPositions 

Bild 1: Klassendiagramm eines Compound-Iterators mit dem konkreten Behavior AllPositions

 

Als Anwender schreiben Sie die blau dargestellte Klasse AllPositions. Hierin geben Sie den Basisiterator und den Subiterator an (hier beide Male ein IntIterator) sowie eine Funktion composeItems, die aus den Cursor-Objekten des Basisiterators und des Subiterators ein Rückgabeobjekt erzeugt.

Anwendung

Die folgende Main-Funktion testet den Compound-Iterator mit dem Behavior AllPositions. Es werden alle Positionen eines 2×3-Spielbretts durchlaufen.

Einfacher geht es mit einer For-Each-Schleife. Dies funktioniert immer dann, wenn eine Klasse (hier: AllPositions) das Interface Iterable implementiert und eine Methode iterator zur Verfügung stellt.

public class TestAllPositions
{
    public static void main(String[] args)
    {
        // Compound-Iterator mit dem AllPositions-Behavior
        Iterator<Position> it=new AllPositions(2, 3).iterator();
        while (it.hasNext())
            System.out.println(it.next());
        System.out.println();

        // oder einfacher mit For-Each-Schleife:
        for (Position p : new AllPositions(2, 3))
            System.out.println(p);
        System.out.println();   
    }
}

Zusammenfassung

Die konkret benötigte Funktionalität eines Compound-Iterators wird in einem Behavior (hier AllPositions) festgelegt. Dies geschieht durch Angabe eines Basisiterators, eines Subiterators und der Funktion composeItems.

Zugrunde gelegt werden dabei getestete Implementierungen der Klassen Compound-Iterator und Compound-IteratorBehavior.

Für das angegebene sehr einfache Beispiel lohnt sich der Aufwand, einen Compound-Iterator zu benutzen, vielleicht nicht. Dadurch aber, dass der Compound-Iterator selbst das Iterator-Interface implementiert, kann er überall eingesetzt werden, wo ein Iterator stehen kann, also z.B. als Basisiterator in einem Filteriterator oder als Subiterator in einem anderen Compound-Iterator.

Anderes Beispiel

Ein anderes Beispiel für die Nutzung eines Compound-Iterators ist ein Iterator, der alle Einträge einer zweidimensionalen ArrayList durchläuft, also einer ArrayList, deren Einträge wiederum ArrayLists sind (wie beim zweidimensionalen Array). Ein solcher Compound-Iterator verwendet das Behavior NestedArrayLists.

 

Weiter mit:   [Implementierung CompoundIterator] [up]

 


H.W. Lang   mail@hwlang.de   Impressum   Datenschutz
Created: 05.02.2008   Updated: 18.02.2023
Diese Webseiten sind während meiner Lehrtätigkeit an der Hochschule Flensburg entstanden