Connaissez-vous l’interface java.lang.Iterable ?
Mais si, vous la connaissez : vous l’utilisez tous les jours, lorsque vous parcourez une Collection. Hé bien, voyons maintenant comment vous pouvez l’utiliser à votre avantage lorsque vous concevez un modèle objet.
Iterable, mode d’emploi
L’interface Iterable signale que la classe qui l’implémente est composée d’un ensemble de sous-éléments que le code appelant peut parcourir.
Elle déclare pour cela une unique méthode iterator(), devant renvoyer un Iterator sur l’ensemble des sous-éléments.
-
public interface Iterable<T> {
-
/**
-
* Returns an iterator over a set of elements of type T.
-
* @return an Iterator.
-
*/
-
Iterator<T> iterator();
-
}
L’interface java.util.Collection étend Iterable, ce qui nous autorise à parcourir ses implémentations (notamment List et Set) de manière uniforme :
-
List<String> words = Arrays.asList(« Hello », « World »);
-
Iterator<String> it = words.iterator();
-
while(it.hasNext()) {
-
System.out.println(it.next());
-
}
C’est également Iterable qui vous permet d’utiliser la boucle « foreach » apparue avec Java 5 (JLS $14.14.2).
EnhancedForStatement:
for ( VariableModifiersopt Type Identifier: Expression) Statement
The Expression must either have type Iterable or else it must be of an array type (§10.1), or a compile-time error occurs.
-
List<String> words = Arrays.asList(« Hello », « World »);
-
for (String word : words) {
-
System.out.println(word);
-
}
Implémenter Iterable dans votre code
Lorsque nous concevons un modèle objet, il arrive fréquemment que nous devions modéliser des relations de type « 1-N avec attributs ». Leur implémentation la plus évidente est une classe possédant possédant des champs correspondant aux attributs de la relation, ainsi qu’une collection encapsulant les sous-éléments.
Une partition musicale en est un bon exemple : elle est constituée d’une séquence de notes (les sous-éléments), ainsi que de divers attributs comme le nom du morceau.
En voici une implémentation naïve :
-
public class Partition {
-
private String nom;
-
private List<Note> notes;
-
public String getNom() {
-
return nom;
-
}
-
public void setNom(String nom) {
-
this.nom = nom;
-
}
-
public List<Note> getNotes() {
-
return notes;
-
}
-
public void setNotes(List<Note> notes) {
-
this.notes = notes;
-
}
-
}
Ce type d’implémentation est très répandu, mais malheureusement très mauvais du point de vue de la conception objet, car il rompt le principe d’encapsulation : l’accesseur de la liste
des notes permet de modifier celle-ci sans que la Partition ne soit avertie, et expose son implémentation interne.
Il faut donc trouver un moyen pour autoriser le développeur à parcourir la liste sans l’exposer directement.
C’est là qu’intervient l’interface Iterable.
Voici le code remanié :
-
public class Partition implements Iterable<Note> {
-
private final String nom;
-
private final List<Note> notes = new ArrayList<Note>();
-
public Partition(String nom, List<Note> notes) {
-
this.nom = nom;
-
if (notes != null) {
-
this.notes.addAll(notes);
-
}
-
}
-
public String getNom() {
-
return nom;
-
}
-
public Iterator<Note> iterator() {
-
return notes.iterator();
-
}
-
}
Ce remaniement amène un second bénéfice, pour l’utilisateur cette fois : il est beaucoup plus facile et naturel de parcourir la partition, note par note.
-
Partition partition = new Partition(« Au clair de la lune », Arrays.asList(C, C, C, D, E, D, C, E, D, D, C));
-
for(Note note : partition) {
-
//Instrument.play(note);
-
}
Conclusion
En conclusion, lorsque vous encapsulez une collection au sein d’une classe, pensez à l’interface Iterable ; votre code sera mieux encapsulé et plus facile à utiliser !
