Java 8: Behavior parametrization ou comment mieux gérer le changement!

java8

Java 8 permet de passer un morceau de code (une fonction ou lambda) à une méthode. Ceci nous encourage à pratiquer le design Behavior parametrization. C'est quoi ce design ? Quels sont ses apports? Par la pratique, nous tentons dans cet article de répondre à ces deux questions.

Design pattern Behavior parametrization

Behavior parameterization est un design pattern de développement ayant pour objectif de mieux gérer (ou d'accompagner) les changements fréquents des exigences fonctionnelles.

Depuis java 8, ceci revient à passer en argument de méthode, un bloc de code exécuté en différé ou au moment approprié.

Décrivons d'abord notre besoin pour cet article. Nous essaierons ensuite d'y répondre avec du code java ancien (pré-lambda) puis avec du java 8 afin d'illustrer ce pattern.

Use-case:

Notre besoin est simple:

Construire la liste des collègues netapsysiens selon les critères suivants :

  • L'année d'intégration de Netapsys,
  • Le pôle actuel.

Nous nous contentons de ces deux critères pour que la démo reste accessible.

On peut bien évidemment imaginer d'autres critères possibles puisque le métier évolue vite et les specs changent régulièrement!

Avec du java pré-lambdas, voici un exemple de code (peu lisible n'est ce pas?) qui peut répondre à notre besoin.

On part d'une classe POJO nommé Salarie avec 3 attributs: id, annee, pole.

Voici la méthode extract que nous allons améliorer au fur et à mesure de notre avancée.

List<Salarie> extract(List<Salarie> salaries, String critAnnee, String critPole) {
        final List<Salarie> liste = new ArrayList();
        for(Salarie salarie: salaries){
            if( critAnnee.equals(salarie.getAnnee()) && 
                   critPole.equals(salarie.getPole()) ){
                liste.add(sl);
            }
        }
        return liste;
    }
Bad code

Pourquoi ce morceau de code est-il mauvais alors que c'est ainsi que l'on faisait toujours?

Au moins une raison peut l'expliquer: Si le métier évolue et que je dois ajouter d'autres critères, je dois réécrire le code pour les rajouter comme arguments de ma (nouvelle) méthode.

Cette démarche de réécrire le code à chaque fois ne peut être acceptable et est source de beaucoup de bugs, sans parler du temps à consacrer à faire la recette des applications concernées, surtout si le module repris est de bas niveau !

Comment peut-on y remédier?

Voici une version intermédiaire, mettant en pratique le design "Bahavior parametrization" qui consiste à:

  • Au lieu de réfléchir aux critères à un niveau de détails trop fin comme le montre les ligne n° 4 à 5 (condition if) du code précédent, nous allons plutôt revoir notre stratégie de test à un niveau plus global, c'est à dire l'entité Salarie.

Ce n'est pas clair!

Un exemple va être plus parlant:

Pour commencer, j'utilise l'interface fonctionnelle java.util.function.Predicate , ayant une seule méthode abstraite (SAM: Single Abstract Method) test(T t) comme suit:

public interface Predicate<T> {
    boolean test(T t);
}
Predicate

C'est cette interface que je passerai en tant que second argument à la méthode précédente extract(List liste, Perdicate<Salarie> pr).

L'intérêt ici, est que je passe un morceau de code (fonction) comme argument à ma méthode, devenu possible avec java 8.

List<Salarie> extract(List<Salarie> sals,Predicate<Salarie> pr){
        final List<Salarie> liste = new ArrayList();
        for(Salarie sal: sals){
            if( pr.test(sal)){
                liste.add(sal);
            }
        }
        return liste;
    }
First example with behavior parametrization

Voici un exemple (inutile et trop verbeux) d'appel de la méthode extract avec une classe anonyme:

final Predicate<Salarie> pr = new Predicate<Salarie>() {
            @Override
            public boolean test(Salarie sal) {
                return "2008".equals(sal.getAnnee()) && 
                       "VeilleTechno".equals(sal.getPole());
            }
        };

        List<Salarie> ret = extract(salaries,pr);
Predicate anonymous class & Behavior

Ce code n'a que trop peu d'intérêt, comme nous allons le démontrer plus loin. L'un des inconvénients, est que je dois écrire une classe anonyme pour chaque nouvelle exigence (c'est à dire nouveau critère d'extraction).

Dans la suite, on verra que l'utilisation des classes anonymes peut être facilement et agréablement remplacée par les lambdas dans java 8.

LA PREMIÈRE BONNE SOLUTION:

Voici donc avant de finir une première solution en java 8 qui s'appuie sur le "Behavior Parametrization".

final Predicate<Salarie> pr = s -> "2008".equals(s.getAnnee()) && "VeilleTechno".equals(s.getPole());

List<Salarie> ret = extract(salaries,pr);
Beahvior and lambdas

Alors là ! Mais c'est quoi ce code bizarre où un type n'est pas typé ? Il ne manque pas une new quelque part?

Voici quelques explications rapides:

  • Nous créons une fonction lambda nommée pr qui cible l'interface fonctionnelle Predicate<Salarie>.
  • L'expression  -> "2008".equals...  définit justement le corps de la méthode test (la SAM de Predicate).
  • Ensuite comme nous avons déjà préparé le terrain par l'introduction du design Behavior Parametrization, on peut appeler le morceau de code (lambda) nommé pr.

LA SOLUTION FINALE

Voici la solution finale, s'appuyant toujours sur le "Behavior Parametrization" et qui concentre les "best practices" que nous ne pouvons détailler ici.

 final Predicate<Salarie> pr = s -> "2008".equals(s.getAnnee()) && "VeilleTechno".equals(s.getPole());

salaries.stream().filter(pr).forEach(System.out::println);
Nice solution with lambdas & Behavior Parametrization

Nous expliquerons tout cela en détail, dans un prochain article sur les Stream dans java 8.

A très vite,

 

 

 

2 commentaires

  1. Super partage. Ça pousse à migrer vers Java 8 pour pouvoir tester parce que c’est clair que ça permet une meilleure gestion des changements et ne parlons pas du gain de temps : )

Laisser un commentaire

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

Captcha *