Predicate In Action

Habituellement, la recherche d'éléments dans une liste consiste à écrire une boucle de parcours d'éléments, un test pour savoir si l'élément parcouru est un de ceux que l'on cherche, puis à ajouter cet élément dans la liste de résultats. L'écriture de ce code est redondant, source d'erreurs et peut devenir complexe à lire et à maintenir.

Les prédicats permettent de simplifier l'écriture de méthodes de recherche d'éléments dans une liste.

En effet, l'interface Predicate permet d'externaliser la méthode de sélection des éléments recherchés.

Nous nous concentrons sur l'intention : que les éléments trouvés correspondent bien à ce que nous cherchons.

Ainsi, nous verrons une recherche classique de livres correspondant à certains critères. Ensuite nous verrons comment les bibliothèques common-collections d'Apache et guava de Google nous permettent de simplifier l'écriture de cette recherche à l'aide notamment de l'interface Predicate.

Exemple de recherche

Nous allons prendre comme exemple une bibliothèque qui dispose d'une liste de livre.

Pour chaque livre, nous disposons des informations suivantes : le titre, l'auteur et l'année de publication.

Nous recherchons les livres répondant aux critères suivant :

  • auteur : Rudyard Kipling
  • titre : le titre comporte les mots "jungle" et "grand"

Voyons comment nous pouvons effectuer cette recherche.

Recherche classique

Voici une boucle de recherche classique :

// Livres trouvés par la recherche
List<Livre> livreTrouves = new ArrayList<Livre>();

// Boucle de recherche
for(Livre livre : livres) {

    // Test des critères
    boolean estTrouve = 
        StringUtils.containsIgnoreCase(livre.getAuteur(),
                                       "Kipling")
     && StringUtils.containsIgnoreCase(livre.getTitre(),
                                       "livre")
     && StringUtils.containsIgnoreCase(livre.getTitre(),
                                       "jungle");

    // Le livre correspond aux critères
    if(estTrouve) {
        livreTrouves.add(livre);
    }
}

Amélioration de la recherche classique

Dans le code précédent, nous remarquons que nous avons une forte complexité au niveau du test sur les critères.
En effet, la logique métier est concentrée dans ce test de vérification des critères.

C'est pourquoi nous créons une méthode spécifiquement chargée d'effectuer ce test.

Nous obtenons ainsi le code suivant :

/**
 * Vérifier si le livre correspond aux critères de recherche.
 * @param livre Livre
 * @return Indique si le livre correspond aux critères.
 */
private boolean verifierCritere(Livre livre)
{
    boolean estTrouve = 
        StringUtils.containsIgnoreCase(livre.getAuteur(),
                                       "Kipling")
     && StringUtils.containsIgnoreCase(livre.getTitre(),
                                       "livre")
     && StringUtils.containsIgnoreCase(livre.getTitre(),
                                       "jungle");

    return estTrouve;
}

La boucle de recherche est simplifiée :

// Livres trouvés par la recherche
List<Livre> livreTrouves = new ArrayList<Livre>();

// Boucle de recherche
for(Livre livre : livres) {

    // Le livre correspond aux critères
    if(verifierCritere(livre)) {
        livreTrouves.add(livre);
    }

}

Prédicat de recherche

La recherche a été simplifiée en externalisant dans une nouvelle méthode le test de vérification des critères sur un des éléments de la liste.

Les bibliothèques commons-collection et guava permettent d'effectuer cette séparation à l'aide de l'interface Predicate.

L'interface Predicate impose l'écriture d'une méthode prenant en paramètre un des éléments de la liste et qui se charge de retourner un booléen indiquant si l'élément correspond à ce que l'on cherche. Dans notre exemple, il s'agissait qu'un livre corresponde à un auteur et à un titre.

Cependant, la méthode principale conserve la logique de parcours des éléments de la liste et d'ajout des éléments trouvés dans la liste de résultats de la recherche. Les bibliothèques common-collections et guava permettent de ne plus avoir à écrire cette logique, tout simplement en fournissant à la méthode de recherche la liste des éléments et le prédicat.

Voyons deux exemples d'implémentation utilisant les deux bibliothèques.

Avec common-collections d'Apache

Nous utilisons l'interface Predicate :

public interface Predicate {
    boolean evaluate(Object object);
}

Voici l'implémentation de notre prédicat :

Predicate predicat = new Predicate() {
    public boolean evaluate(Object o) {
        Livre livre = (Livre) o;
        boolean estTrouve = 
            StringUtils.containsIgnoreCase(livre.getAuteur(),
                                           "Kipling")
         && StringUtils.containsIgnoreCase(livre.getTitre(),
                                           "livre")
         && StringUtils.containsIgnoreCase(livre.getTitre(),
                                           "jungle");

        return estTrouve;
    }
};

La méthode evaluate correspond à la méthode verifierCritere de notre code précédent.

La version officielle des commons-collection ne supporte pas les types génériques. Il existe cependant une version dérivée supportant les types génériques et qui sera indiquée à la fin de ce post.

Voici notre boucle de recherche qui utilise maintenant le prédicat :

// Livres trouvés par la recherche
List<Livre> livreTrouves = new ArrayList<Livre>();

// Boucle de recherche
for(Livre livre : livres) {
    // Test des critères
    if(predicat.evaluate(livre)) {
        // Le livre correspond aux critères
        livreTrouves.add(livre);
    }
}

Cette boucle de recherche peut être remplacée par l'appel de la méthode select de la classe CollectionUtils :

// Livres trouvés par la recherche
List<Livre> livreTrouves = new ArrayList<Livre>();

// Recherche de livres
CollectionUtils.select(livres,
                       predicate,
                       livreResultats);

Avec guava de Google

Même chose que précédemment, nous utilisons l'interface Predicate, mais cette fois-ci celle de guava :

public interface Predicate<T> {
    boolean apply(T input);
}

La bibliothèque guava supporte par défaut les types génériques ce qui simplifie l'écriture.

Voici l'implémentation de notre prédicat :

Predicate<Livre> predicate = new Predicate<Livre>() {
    public boolean apply(Livre livre)
        boolean estTrouve = 
            StringUtils.containsIgnoreCase(livre.getAuteur(),
                                           "Kipling")
         && StringUtils.containsIgnoreCase(livre.getTitre(),
                                           "livre")
         && StringUtils.containsIgnoreCase(livre.getTitre(),
                                           "jungle");

        return estTrouve;
    }
};

Voici notre boucle de recherche qui utilise maintenant le prédicat :

// Livres trouvés par la recherche
List<Livre> livreTrouves = new ArrayList<Livre>();

// Boucle de recherche
for(Livre livre : livres) {
    // Test des critères
    if(predicat.apply(livre)) {
        // Le livre correspond aux critères
        livreTrouves.add(livre);
    }
}

Cette boucle de recherche peut être remplacée par l'appel de la méthode filter de la classe Collections2 :

// Recherche de livres
Collection<Livre> livreResultats = 
    Collections2.filter(bibliotheque.getLivres(),
                         predicate);

Conclusion

L'utilisation des prédicats pour la recherche permet de se concentrer sur l'intention : vérifier que les éléments trouvés correspondent bien aux critères de recherche.

Références

Common-collections d'Apache

Site

Dépendance Maven

<dependency>
	<groupId>commons-collections</groupId>
	<artifactId>commons-collections</artifactId>
	<version>3.2.1</version>
</dependency>

Common-collections avec les types génériques

Site

Dépendance Maven

<dependency>
    <groupId>net.sourceforge.collections</groupId>
    <artifactId>collections-generic</artifactId>
    <version>4.01</version>
</dependency>

Guava de Google

Site

Dépendance Maven

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>13.0.1</version>
</dependency>

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.