Návrhový vzor Iterátor (Iterator) slouží k vytvoření rozhraní, které umožňuje sekvenčně procházet objekty uložené v nějaké složité struktuře (kontejneru). Samotná implementace tohoto rozhraní zůstává uživateli skryta. Iterátor patří mezi tzv GoF (Gang of Four) vzory – je součástí bible návrhových vzorů – knihy Design Patterns: Elements of Reusable Object-Oriented Software.
Motivace
Iterátor použijeme v několika částečně se překrývajících situacích.
Průchod složitého objektu
První z nich je, chceme-li procházet objekt, jehož struktura není na první pohled zřejmá. Příkladem může být třída Rodina, ve které existuje velké množství vazeb (rodič, potomek, švagr, teta, sestřenice...). Rozhraní iterátoru nám zaručí, že projdeme systematicky všechny členy rodiny (žádného nevynecháme, ani nezpracujeme vícekrát).
Jednotné rozhraní
Druhou situací je, když chceme vytvořit jednotné rozhraní pro mnoho různých kolekcí. Toto rozhraní pak implementujeme například pro spojový seznam, strom, dynamické pole a podobně. V programu má pak uživatel možnost procházet všechny kolekce jednotným způsobem, aniž by se musel zajímat o to, jak jsou data uložená.
Optimalizace průchodu
Třetím uplatněním iterátoru je optimalizace průchodu kolekcí. Uvažme spojový seznam, který pro přístup používá pouze běžně implementovanou metodu get (pokud chceme n-tý prvek, tak musíme přeiterovat vždy n-1 předchozích uzlů). Pokud bychom chtěli projít postupně všechny prvky, tak potřebujeme operací. V případě použití iterátoru, který by si držel stav (pozici ve spojovém seznamu) a vždy pouze přešel o uzel dál, by počet operací klesl na
.
Příklad
Implementujte iterátor pro jednosměrně zřetězený spojový seznam. Iterátor bude obsahovat metody hasNext – dotaz na existenci dalšího prvku – a next – přechod na další prvek (uzel). Navrácený iterátor bude pro usnadnění průchodu kolekcí vždy inicializován na pozici -1 (tj. před první prvek).
001.
/**
002.
* Jednosmerne zretezeny spojovy seznam
003.
* @author Pavel Micka
004.
* @param <ENTITY> typovy parametr specifikujici ukladany typ
005.
*/
006.
public
class
MyLinkedList<ENTITY>{
007.
008.
private
Node first;
009.
private
Node last;
010.
private
int
size;
011.
012.
/**
013.
* Konstruktor spojoveho seznamu
014.
*/
015.
public
MyLinkedList() {
016.
this
.size =
0
;
017.
}
018.
019.
/**
020.
* Vlozi na konec seznamu prvek
021.
* @param i prvek k vlozeni
022.
*/
023.
public
void
add(ENTITY i) {
024.
Node n =
new
Node(i);
025.
if
(size ==
0
) {
026.
this
.first = n;
027.
this
.last = n;
028.
}
else
{
029.
this
.last.next = n;
030.
this
.last = n;
031.
}
032.
size++;
033.
}
034.
035.
/**
036.
* Vrati prvek na indexu i
037.
* @param i index prvku
038.
* @throws IndexOutOfBoundsException pokud je i vyssi nebo rovna delce seznamu
039.
* @throws IllegalArgumentException pokud je i zaporne
040.
* @return prvek na indexu i
041.
*/
042.
public
ENTITY get(
int
i) {
043.
if
(i >= size) {
044.
throw
new
IndexOutOfBoundsException(
"Mimo meze index:"
+ i +
", size:"
+
this
.size);
045.
}
046.
if
(i <
0
) {
047.
throw
new
IllegalArgumentException(
"Index mensi nez 0"
);
048.
}
049.
Node curr = first;
050.
for
(
int
j =
0
; j < i; j++) {
051.
curr = curr.next;
052.
}
053.
return
curr.value;
054.
}
055.
056.
/**
057.
* Smaze prvek na indexu i
058.
* @param i index mazaneho prvku
059.
* @throws IndexOutOfBoundsException pokud je i vyssi nebo rovna delce seznamu
060.
* @throws IllegalArgumentException pokud je i zaporne
061.
*/
062.
public
void
remove(
int
i) {
063.
if
(i >= size) {
064.
throw
new
IndexOutOfBoundsException(
"Mimo meze index:"
+ i +
", size:"
+
this
.size);
065.
}
066.
if
(i <
0
) {
067.
throw
new
IllegalArgumentException(
"Index mensi nez 0"
);
068.
}
069.
if
(i ==
0
) {
070.
first = first.next;
071.
}
else
{
072.
Node curr = first;
073.
for
(
int
j =
0
; j < i -
1
; j++) {
//najdeme predchozi
074.
curr = curr.next;
075.
}
076.
curr.next = curr.next.next;
//a mazany prvek vynechame
077.
if
(i == size -
1
) {
//pokud mazeme posledni
078.
last = curr;
079.
}
080.
}
081.
size--;
082.
}
083.
084.
/**
085.
* Dotaz na delku seznamu
086.
* @return delka seznamu
087.
*/
088.
public
int
size() {
089.
return
this
.size;
090.
}
091.
092.
/**
093.
* Klasicka toString metoda, vraci textovou reprezentaci objektu
094.
* @return textova reprezentace objektu
095.
*/
096.
@Override
097.
public
String toString() {
098.
StringBuilder builder =
new
StringBuilder();
099.
Node curr = first;
100.
for
(
int
i =
0
; i <
this
.size; i++) {
101.
builder.append(curr.value);
102.
builder.append(
" "
);
103.
curr = curr.next;
104.
}
105.
return
builder.toString();
106.
}
107.
108.
public
Iterator<ENTITY> iterator() {
109.
return
new
LinkedListIterator();
110.
}
111.
112.
/**
113.
* Vnitrni trida reprezentujici jeden uzel spojoveho seznamu
114.
*/
115.
private
class
Node {
116.
117.
private
ENTITY value;
118.
private
Node next;
119.
120.
private
Node(ENTITY value) {
121.
this
.value = value;
122.
}
123.
}
124.
125.
/* *************************************************
126.
* ITERATOR *
127.
* *************************************************/
128.
129.
/**
130.
* Jednoduchy iterator pro spojovy seznam
131.
* Neresi situaci, kdy je iteratoru pod rukou pozmena kolekce (tj. neni fail fast)
132.
* @param <ENTITY> typovy parametr specifikujici typ ulozeny v prochazene kolekce
133.
*
134.
* Poznamka: Jelikoz je iterator vytvaren jako soukroma vnitrni trida, tak
135.
* je dokanale skryt (zapouzdren) pred okolnim svetem. Veskera jeho volani
136.
* mohou byt uskutecnena pouze skrze inteface iterator
137.
*
138.
* Poznamka2: V realnem Java svete bychom pouzili pro iterator interface
139.
* java.util.Iterator a pro iterovanou tridu interface java.lang.Iterable.
140.
* Tato implementace slouzi pouze jako ukazka vzoru samotneho bez navaznosti
141.
* na tato Java-specific rozhrani
142.
*/
143.
private
class
LinkedListIterator
implements
Iterator<ENTITY>{
144.
/**
145.
* Odkaz na soucasny uzel spojoveho seznamu
146.
*/
147.
private
Node currNode;
148.
/**
149.
* Priznak toho, ze je iterator nastaven na -1 prvek
150.
* (tj. jeste nebyl pouzit k prechodu na prvni prvek)
151.
*/
152.
private
boolean
start;
153.
154.
public
LinkedListIterator() {
155.
this
.currNode =
null
;
156.
this
.start =
true
;
157.
}
158.
159.
@Override
160.
public
boolean
hasNext() {
161.
if
(start) {
162.
return
first !=
null
;
163.
}
164.
return
currNode.next !=
null
;
165.
}
166.
167.
@Override
168.
public
ENTITY next() {
169.
if
(start) {
170.
start =
false
;
171.
currNode = first;
172.
}
else
{
173.
currNode = currNode.next;
174.
}
175.
return
currNode.value;
176.
}
177.
}
178.
}
179.
180.
/**
181.
* Rozhrani iteratoru. Iterator slouzi k sekvencnimu pruchodu pres objekty ulozene v kolekci/kontejneru
182.
* @author Pavel Micka
183.
* @param <ENTITY> typovy parametr iteratoru
184.
*/
185.
interface
Iterator<ENTITY>{
186.
/**
187.
* Dotaz na existenci dalsiho prvku
188.
* @return @true, pokud dalsi prvek existuje, @false v opacnem pripade
189.
*/
190.
public
boolean
hasNext();
191.
/**
192.
* Navrati dalsi entitu
193.
* @return dalsi entita ("dalsi" je zavisle na konkretni implementaci iteratoru)
194.
*/
195.
public
ENTITY next();
196.
}