BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Actualités Venkat Subramaniam Modernise Les Modèles De Conception GoF Avec Java Moderne Au Devoxx BE

Venkat Subramaniam Modernise Les Modèles De Conception GoF Avec Java Moderne Au Devoxx BE

Favoris

Les modèles de conception GoF publiés en 1998, considérés comme un classique de l'informatique, sont  toujours enseignés dans les universités et perçus comme faisant partie des meilleures pratiques dans les milieux industriels. Lors de son discours sur la plongée approfondie au Devoxx Belgium, Venkat Subramaniam leur a donné une touche contemporaine en implémentant Iterator, Strategy, Decorator ou Factory Method à Java Moderne.

Au tout début de sa conférence, Subramaniam a comparé les auteurs du livre à des grands-pères du développement logiciel et leurs design patterns à des recettes de grand-mère : même si vous les avez, vous ne pourrez pas reproduire le plat. En effet, il considère que les patrons de conception sont exeptionnels en tant qu'outil de communication mais un désastre en tant qu'outil de conception de logiciels.

Voici les schémas habituels que nous pourrions rencontrer dans notre codage quotidien et qu'il a rendu un peu plus fluide à travers une démonstration énergique et joyeuse.

L'iterator pattern a beaucoup changé en raison de l'adoption par Java de la programmation fonctionnelle. L'un des changements les plus importants a été le passage d'un iterator externe à un iterator interne, fourni avec l'API fonctionnelle de Java. Avec ce changement, vous pouvez passer de l'utilisation de l'itération de style impératif verbeux.....
 

int count = 0;
for(var name: names) {
   if(name.length() == 4) {
     System.out.println(name.toUpperCase());
	 count++;

     if(count == 2) {
        break;
     }
   }
  }
}

à l'itération fonctionnelle fluide :

names.stream()
     .filter(name -> name.length() == 4)
     .map(String::toUpperCase)
     .limit(2)
     .forEach(System.out::println);

 

Les limit(long) et takeWhile(Predicate<? super T>) (ajoutés dans Java 9) sont les équivalents fonctionnels des instructions continue et break, la première ne prend qu'une limite numérique tandis que la seconde utilise une expression.

Même si l'API fonctionnelle de Java fait partie du JDK depuis près d'une décennie, il existe encore des erreurs courantes qui persistent dans les bases de code. Quand le pipeline fonctionnel n'est *pas* pur (il change ou dépend de tout état visible de l'extérieur).alors les résultats des opérations d'itération sont rendus imprévisibles (en particulier dans les exécutions parallèles).

Stratégie Lightweight - quand nous voulons changer une petite partie d'un algorithme tout en gardant le reste de l'algorithme identique. Historiquement, le modèle a été réalisé à partir d'une méthode qui ne prend qu'une seule interface comme paramètre et qui contient plusieurs implémentations pour chacune des stratégies à implémenter. En effet, les stratégies correspondent souvent qu'à une seule méthode ou fonction. De fait, les interfaces fonctionnelles et les lambdas fonctionnent très bien.Par ailleurs, même si les classes anonymes représentaient un mécanisme d'implémentation, les interfaces fonctionnelles (Predicate<? super T> est un bon candidat) ou les lambdas rendent le code beaucoup plus fluide et plus facile à comprendre. Dans Java moderne, la stratégie ressemble plus à une fonctionnalité qu'à un modèle dont la mise en œuvre nécessite des efforts importants.
 

public class Sample {
  public static int totalValues(List<Integer> numbers) {
    int total = 0;

    for(var number: numbers) {
      total += number;
    }

    return  total;
  }

  public static int totalEvenValues(List<Integer> numbers) {
    int total = 0;

    for(var number: numbers) {
      if(number % 2 == 0) { total += number; }
    }

    return  total;
  }

  public static int totalOddValues(List<Integer> numbers) {
    int total = 0;

    for(var number: numbers) {
      if(number % 2 != 0) { total += number; }
    }

    return  total;
  }


  public static void main(String[] args) {
    var numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    System.out.println(totalValues(numbers));
    System.out.println(totalEvenValues(numbers));
    System.out.println(totalOddValues(numbers));
  }
}

La prise la plus moderne serait d'utiliser un lambda pour la stratégie :

import java.util.function.Predicate;

public class Sample {
  public static int totalValues(List<Integer> numbers,
    Predicate<Integer> selector) {
    int total = 0;

    for(var number: numbers) {
      if(selector.test(number)) {
        total += number;
      }
    }

    return  total;
  }

  public static boolean isOdd(int number) {
    return number % 2 != 0;
  }

  public static void main(String[] args) {
    var numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    System.out.println(totalValues(numbers, ignore -> true));
    System.out.println(totalValues(numbers, 
      number -> number % 2 == 0));

    System.out.println(totalValues(numbers, Sample::isOdd));
  }
}

 

Factory Method qui utilise des méthodes par défaut

Dans son introduction sur la mise en œuvre de la Factory method, Venkat a déclaré ce qui suit :

Quel est le pire mot clé en Java du point de vue du polymorphisme ? [...] Même si 'final', 'instanceof' et 'static' sont de bons candidats pour cela, ce sont des mininions. new est la mafia de chacun d'eux. 

De nombreux patterns (patterns créationnels), frameworks (Spring, Guice) ou techniques ont été conçus afin de répondre à la « méchanceté » du new, son manque de support polymorphisme et son couplage étroit. Inspiré par la capacité polymorphe de Ruby à créer différents objets en fonction du contexte, Venkat propose une implémentation du modèle de Factory method en utilisant le mot-clé de Java par défaut. Cette approche permettrait d'utiliser des interfaces et de très petites classes d'implémentation ce qui rendrait le code plus facile à suivre.

import java.util.*;

interface Pet {}
class Dog implements Pet {}
class Cat implements Pet {}

interface Person {
  Pet getPet();

  default void play() {
    System.out.println("playing with " + getPet());
  }
}

class DogPerson implements Person {
  private Dog dog = new Dog();

  public Pet getPet() { return dog; }
}

class CatLover implements Person {
  private Cat cat = new Cat();
  public Pet getPet() { return cat; }
}

public class Sample {
  public static void call(Person person) {
    person.play();
  }

  public static void main(String[] args) {
    call(new DogPerson());
    call(new CatLover());
  }
}

 

Decorator

Même si le pattern décorator est bien connu en théorie par de nombreux programmeurs, peu l'ont réellement implémenté dans la pratique. L'exemple le plus tristement célèbre de son implémentation est probablement la construction de packages io. Venkat propose une approche différente de ce modèle, basée sur la composabilité des fonctions : en utilisant la fonction d'identité and andThen(Function<? super R,? étend V> ), il a la capacité de construire des mécanismes simples et fluides qui améliorent les capacités d'un objet.

 

class Camera {
  private Function<Color, Color> filter;

  public Camera(Function<Color, Color>... filters) {
    filter = Stream.of(filters)
      .reduce(Function.identity(), Function::andThen);
  }

  public Color snap(Color input) {
    return filter.apply(input);
  }
}

public class Sample {
  public static void print(Camera camera) {
    System.out.println(camera.snap(new Color(125, 125, 125)));
  }

  public static void main(String[] args) {
    print(new Camera());

    print(new Camera(Color::brighter));
    print(new Camera(Color::darker));

    print(new Camera(Color::brighter, Color::darker));
  }
}

Même si les patterns semblent éternellement jeunes, comme l'a mentionné Subramaniam lors de son discours : "Les modèles de conception interviennent souvent pour combler les lacunes d'un langage de programmation. Plus un langage est puissant moins nous parlons de modèles de conception car ceux-ci deviennent naturellement les caractéristiques de la langue."
 

Avec l'évolution des langages de programmation et notre expérience, les modèles évoluent également au fil du temps. Certains d'entre eux sont assimilés à des caractéristiques des langages, d'autres ont été jugés obsolètes et d'autres sont plus faciles à mettre en œuvre. Quelle que soit la catégorie dans laquelle appartient votre favori, Venkat suggère de les utiliser comme moyen de communication et de laisser le code évoluer vers eux. En outre, il recommande d'expérimenter plusieurs langages de programmation afin de rendre votre codage plus fluide.

Au sujet de l’Auteur

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT