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.
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.
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).
Als Basisiterator und als Subiterator wird ein Generator verwendet, der nacheinander die Zahlen von 0 bis n-1 liefert.
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.
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
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.
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.
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.
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]