Iterator

La programmation avec design patterns

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
L'itération est une notion bien connue à des développeurs de tous les langages. Cependant, cette notion est également un design pattern, même si la différence entre les deux n'est pas très significative.

Le design pattern iterator (l'itérateur) appartient à la famille des patrons comportementaux. Sa définition simplifie pourrait être "un objet qui garantit l'accès à une collection d'objets". Si l'on fait l'approchement avec les itérateurs en Java, on s'aperçoit que les objets d'une collection ne sont pas exposés directement. C'est l'itérateur qui retourne l'instance recherchée avec la méthode (next()). Et cette absence d'exposition des objets principaux est la première caractéristique de l'itérateur.

Un autre point le caractérisant est la possibilité de traverser les collections d'une manière universelle. Les mêmes méthodes nous serviront pour parcourir ArrayList et LinkedList. Cette universalité rend le code plus lisible et plus facilement réutilisable dans d'autres projets.

Exemple d'itérateur
Dans le monde réel, un exemple d'itérateur peut être la boîte de vitesse. Pour les besoins de simplicité, on admet qu'elle ne peut marcher qu'en une seule direction.

// Aggregate
interface AggregateInterface{
public IteratorInterface createIterator();
}

class Gearbox implements AggregateInterface{
private final List<Speed> speeds;

public Gearbox(final List<Speed> speeds){
this.speeds=speeds;
}

@Override
public IteratorInterface createIterator(){
return new SpeedIterator(this.speeds);
}
}

Dans ce bout de code on voit le premier composant de l'itérateur : aggregator (l'agrégat). Généralement une interface, elle définit la façon d'obtenir l'itérateur. Dans notre exemple cela se présente par la méthode createIterator(). C'est aussi lui qui contient les références vers les éléments à parcourir.


// Iterator
interface IteratorInterface{
public Object next();

public boolean hasNext();
}

class SpeedIterator implements IteratorInterface{
private final List<Speed> speeds;
private int actual=-1;

public SpeedIterator(final List<Speed> speeds){
this.speeds=speeds;
}

@Override
public Speed next(){
return this.speeds.get(this.actual);
}

@Override
public boolean hasNext(){
this.actual++;
return this.actual<this.speeds.size();
}
}

L'autre composant du design pattern est l'itérateur. Il définit la façon de parcourir les éléments (next()) ainsi que les méthodes de contrôle (hasNext()).


import java.util.ArrayList;
import java.util.List;

public class IteratorSample{
public static void main(final String[] args){
final List<Speed> speeds=new ArrayList<Speed>();
speeds.add(new Speed("#1"));
speeds.add(new Speed("#2"));
speeds.add(new Speed("#3"));
speeds.add(new Speed("#4"));
speeds.add(new Speed("#5"));
final Gearbox gearbox=new Gearbox(speeds);
final IteratorInterface iterator=gearbox.createIterator();
while(iterator.hasNext()){
System.out.println("Got speed : "+iterator.next());
}
}
}

// entity class
class Speed{
private final String name;

public Speed(final String name){
this.name=name;
}

@Override
public String toString(){
return this.name;
}
}

Ci-dessus la classe client et l'entité représentant la vitesse. L'itération s'effectue sur la base des deux méthodes déjà mentionnées : une de contrôle d'accès, l'autre de récupération d'un élément.


Got speed : #1
Got speed : #2
Got speed : #3
Got speed : #4
Got speed : #5


Itérateur ou indexation ?
Un autre moyen d'accès à des éléments d'une collection s'appelle l'indexation. Il permet d'accéder à un élément par rapport à son index (par exemple : la clé dans une Map). Cependant, l'itération possède plusieurs avantages par rapport à ce mécanisme plutôt privilégié dans les langages procéduraux. L'itération garantit l'accès à tous les éléments, même à ceux qui n'ont pas un accès par index implémenté (on ne peut pas faire myArray[i]). L'itérateur est une structure plus complexe que l'indexation. Il permet d'implémenter des opérations additionnelles, comme la vérification d'accès. On pourrait faire de même dans l'indexation, mais sans pouvoir le généraliser. Et cette universalité est le troisième gros avantage de l'itérateur.

L'indexation pourrait suffire à de structures simples qui nécessitent l'accès uniquement en lecture. Pour le constater, il suffit d'effectuer l'opération de suppression sur une List en Java. Dans le cas d'utilisation de l'indexation, la suppression provoquera une exception. Par contre, elle se déroulera bien avec l'utilisation de l'itérateur. La comparaison est visible dans ce code :

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorDeleteTest {

private static List<String> letters = new ArrayList<String>() {{
add("A");
add("B");
add("C");
add("DD");
add("EE");
add("E");
}};

private static List<String> lettersForeach = new ArrayList<String>() {{
add("A");
add("B");
add("C");
add("DD");
add("EE");
add("E");
}};

public static void main(String[] args) {
// iterator case
Iterator<String> lettersIterator = letters.iterator();
while (lettersIterator.hasNext()) {
String letter = (String) lettersIterator.next();
if (letter.length() > 1) {
lettersIterator.remove();
}
}
System.out.println("Letters after filter : "+letters);

// foreach case
int i = 0;
for (String letter : lettersForeach) {
if (letter.length() > 1) {
letters.remove(i);
}
i++;
}
System.out.println("Letters after filter : "+lettersForeach);
}

}

Le résultat qu'on verra sur l'écran est le suivant :

Letters after filter : [A, B, C, E]
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 4, Size: 3
at java.util.ArrayList.rangeCheck(ArrayList.java:571)
at java.util.ArrayList.remove(ArrayList.java:412)
at junit.IteratorDeleteTest.main(IteratorDeleteTest.java:42)


On voit donc clairement que l'itérateur est un moyen universel permettant d'analyser les collections. Il est plus puissant que l'indexation normale car permet de contrôler l'accès à des éléments de collection d'une manière uniformisée. En outre, il peut également être employé dans des opérations d'écriture, comme par exemple suppression d'un élément. L'itérateur peut également être thread safe par définition. Il suffisait alors de copier la collection dans un attribut de l'agrégat à la place de pointer vers elle.
Bartosz KONIECZNY 27-11-2013 06:09 design patterns
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil JavaScript

Comment accéder aux éléments du parent avec jQuery ?

jQuery, le célèbre framework JavaScript, permet d'écrire moins et de faire plus. On pourrait s'imaginer que cela s'applique uniquement aux opérations basiques, limitées au sein d'un seul document / d'une seule fenêtre. Cependant, avec jQuery on peut aussi manipuler les éléments d'une fenêtre-enfant. Ces opérations se limitent à la précision qu'il s'agit du document parent. Supposons qu'on ouvre une popup et qu'on veut récupérer les informations appartenant aux div avec l'identifiant "CONTENT" situé dans le document parent. Pour ce faire, il suffit de préciser qu'il s'agit du document supérieur :

$('#CONTENT', window.parent.document).html();