Ch. 1 — Patrons de conception
Ce qu'est un patron, les trois familles, et les six patrons de base : Fabrique simple, Décorateur, Composite, Singleton, Stratégie et ObjetNull — avec leurs exemples Java.
La définition à retenir
« Un patron de conception est une solution générique, orientée objets, proposant une structuration de classes permettant de résoudre un problème spécifique récurrent. »
Trois mots comptent dans cette phrase : générique (le patron n'est pas un code prêt à coller, il s'adapte), récurrent (le problème revient souvent, ça vaut le coup d'avoir une réponse toute faite), et structuration de classes (un patron se décrit en UML, pas en lignes de code).
Vous en connaissez déjà sans le savoir : l'Itérateur (parcourir une collection sans connaître son organisation interne), le Singleton (une seule instance), la Fabrique (créer des objets sans dire leur classe concrète).
Pourquoi un vocabulaire de haut niveau
Sans patron, une intention se raconte en une phrase interminable :
- « On va externaliser du code dédié à la construction d'instances de différents éléments de la hiérarchie de classes en fonction des valeurs des paramètres reçus » ;
- avec le patron : « On va recourir à une Fabrique ».
Le vocabulaire des patrons permet de concevoir à plus gros grain, de planifier de façon plus abstraite — et d'expliquer le code de façon concise entre collègues.
Les trois familles
Un patron se classe selon ce à quoi il répond :
Comment se décrit un patron
Chaque patron du catalogue suit le même plan :
Identification — son nom et sa classification (créateur, structural, comportemental)
Problème — l'intention, un scénario d'exemple, les situations où il s'applique
Solution — la structure UML, les participants et leur collaboration, un exemple d'implémentation
Conséquences — les compromis d'utilisation, les problèmes connus
C'est ce plan qu'il faut savoir remplir à la main pour les six patrons du cours.
Les six patrons de base
Décorateur (structural)
Permet d'attacher — ou d'enlever — en cours d'exécution de nouvelles responsabilités à un objet, et donc aussi de modifier son comportement. Le décorateur est du même type que ce qu'il décore, tout en contenant l'objet décoré.
L'exemple canonique est java.io :
new BufferedInputStream(
new FileInputStream(
new File("/some/file.name")));Chaque emballage ajoute une capacité (BufferedInputStream met en cache, FileInputStream lit un
fichier) — et les objets se composent à l'exécution.
Ce n'est pas (que) de l'héritage
Il y a plusieurs objets : l'objet BufferedInputStream contient un objet FileInputStream.
L'héritage donne le type commun, la délégation fait le travail. C'est toute la subtilité du
patron.
Composite (structural)
Permet de représenter une arborescence d'objets : tous les objets sont traités de la même façon, qu'ils soient une feuille, un nœud intermédiaire ou la racine.
L'exemple de JavaFX : Button et TextField sont des Node feuilles, HBox est un Node
qui peut contenir d'autres Node — le même message resize s'envoie à tous, sans jamais
demander « es-tu un conteneur ? ».
Singleton (créateur)
Assure qu'il n'y a (au plus) qu'une seule instance d'une classe donnée, et permet de récupérer cette instance unique :
class Snippet {
private static Snippet singleton = null;
private Snippet() { } // constructeur privé : personne ne peut faire new
public static Snippet getInstance() {
if (singleton == null)
singleton = new Snippet();
return singleton;
}
}Abus dangereux
Le Singleton est aussi considéré comme un anti-patron : souvent mal utilisé, il cache un état
global qui rend les tests et la maintenance pénibles. C'est une des rares utilisations
légitimes de static — mais demandez-vous toujours si une instance unique est vraiment
nécessaire avant d'en écrire un.
Stratégie (comportemental)
Permet de sélectionner à l'exécution un algorithme parmi plusieurs disponibles. Exemple du cours : différents algorithmes de tri interchangeables — on choisit lequel employer au moment de l'exécution, sans réécrire le code qui l'utilise.
ObjetNull (null object)
En Java, « pas d'objet » se note null — mais on ne peut pas envoyer un message à un objet qui
n'existe pas (NullPointerException). On finit donc par tester partout :
/* recherche une figure en position (x, y) */
Figure figure = figureAt(posX, posY);
if (figure != null) {
figure.draw();
}Autre solution : créer une classe NullObject qui accepte tous les messages et ne fait rien :
public abstract class Figure {
public abstract void draw();
}
public class NullFigure extends Figure {
@Override
public void draw() { /* nothing */ }
}On ne retourne plus null mais une instance de NullFigure, et le code appelant devient :
Figure figure = figureAt(posX, posY);
figure.draw(); // plus aucun if : dessiner « rien » est un comportementPas de « vache sacrée »
Le message de fin du cours
L'utilisation des patrons peut et doit être adaptée à chaque situation. Il faut comprendre le problème récurrent et la solution générique pour savoir adapter le patron — chaque patron a ses avantages et ses inconvénients, et c'est en les comprenant qu'on décide quand et pourquoi les utiliser… ou pas. Ne cherchez pas à les utiliser à tout prix.
Deux remarques pour compléter :
- les patrons architecturaux sont des solutions de plus grande ampleur, qui concernent un grand nombre de classes (voire toutes celles du système) — exemple : MVC, à venir dans le cours ;
- il existe de nombreux autres patrons : le catalogue historique compte 23 patterns (Gang of Four, 1994) et de nouveaux sont encore proposés. Référence accessible : refactoring.guru/fr.
A retenir
- un patron = une solution générique à un problème spécifique récurrent, exprimée comme une structuration de classes ;
- connaître et comprendre les patrons les plus courants est une pratique indispensable ;
- savoir les adapter au contexte est une source d'inspiration, pas une contrainte.
ch1-patrons-de-conception.pdf
R3.04 · Cours 1 — Fabien Delecroix, base de Nicolas Anquetil · 29 diapositives
Chargement du document…
Dernière mise à jour le
Ch. 0 bis — Rappels de POO et tests
Objets, classes, encapsulation, héritage, interfaces, classes abstraites, transtypage et tests JUnit 5 — les prérequis du S2 remis au propre.
Ch. 2 — Observable et observateurs
Programmation événementielle, abonnement, notification, réaction : le patron Observer — comment un objet prévient d'autres objets d'un changement sans rien connaître d'eux.