Les Patrons de Conception
Un patron de conception (Design Pattern) est une solution à un problème récurrent dans un contexte donné. On peut considérer un patron de conception comme une formalisation de bonnes pratiques, ce qui signifie qu'on privilégie les solutions éprouvées. Les patrons de conception suivent un format particulier :
- Une description du problème avec un exemple concret et une solution qui lui est spécifique
- Un résumé des choses à considérer pour formuler une solution générale
- La solution générale
- Les conséquences positives et négatives de l'utilisation de la solution
- Une liste des patrons qui sont liés
Différentes Catégories de Patrons de Conception
Il existe trois catégories principales de patrons de conception :
1. Patrons de Création
Ces patrons concernent le processus de création des objets. Ils incluent :
- Fabrique abstraite (Abstract Factory)
- Monteur (Builder)
- Fabrique (Factory Method)
- Prototype (Prototype)
- Singleton (Singleton)
2. Patrons de Structure
Ces patrons sont liés à la composition des classes ou objets. Ils incluent :
- Adaptateur (Adapter)
- Pont (Bridge)
- Objet composite (Composite)
- Décorateur (Decorator)
- Façade (Facade)
- Poids-mouche ou poids-plume (Flyweight)
- Proxy (Proxy)
3. Patrons de Comportement
Ces patrons caractérisent les façons dont les classes ou les objets interagissent ou distribuent les responsabilités. Ils incluent :
- Chaîne de responsabilité (Chain of Responsibility)
- Commande (Command)
- Interpréteur (Interpreter)
- Itérateur (Iterator)
- Médiateur (Mediator)
- Memento (Memento)
- Observateur (Observer)
- État (State)
- Stratégie (Strategy)
- Patron de méthode (Template Method)
- Visiteur (Visitor)
1. Singleton
Le patron Singleton garantit qu'une classe n'a qu'une et une seule instance. Tous les objets qui utilisent une instance de cette classe utilisent la même instance.
Exemples d'utilisation :
- Gestion centralisée d'une ressource interne, comme un compteur global
- Gestion centralisée d'une ressource externe, comme un objet qui gère la réutilisation d'une connexion à une base de données
- Classes qui ne devraient avoir qu'une seule instance à la fois, telles que l'horloge du système, la fenêtre principale d'une application ou un générateur de nombres aléatoires
Exemple de code en Java :
public class Singleton {
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void setValue(int value) {
i = value;
}
public int getValue() {
return i;
}
private Singleton() {
System.out.println("Construction du Singleton");
}
private static Singleton instance;
private int i;
}
2. Observateur
Le patron Observateur permet de créer des dépendances dynamiques entre objets. Un objet s'inscrit envers un autre objet, et l'objet "observé" notifie les objets "inscrits" de ses changements d'état.
Exemples d'utilisation :
- Détecteur de fumée
Exemple de code en Java :
class Signal extends Observable {
void setData(byte[] lbData) {
setChanged();
notifyObservers();
}
}
class JPanelSignal extends JPanel implements Observer {
void init(Signal lSigAObserver) {
lSigAObserver.addObserver(this);
}
void update(Observable observable, Object objectConcerne) {
repaint();
}
}
3. Composite
Le patron Composite permet de concevoir et construire des objets complexes en utilisant la composition récursive d'objets similaires, représentée par un arbre. Il est aussi connu sous le nom de composition récursive.
Exemples d'utilisation :
- Programme de traitement de texte : met en forme les caractères en lignes de texte organisées en colonnes qui sont elles-mêmes organisées en pages
- Liens complexes : un document contient d'autres éléments, des colonnes et pages peuvent contenir des cadres, etc.
4. Médiateur
Le patron Médiateur définit un objet qui encapsule comment un ensemble d'objets interagissent. Il est utilisé pour réduire les dépendances entre plusieurs classes.
Exemple d'utilisation :
- Boîte de dialogue dans une interface graphique utilisateur. Une boîte de dialogue utilise une fenêtre pour présenter une collection de composants graphiques comme des boutons, menus, champs. Souvent, il existe des dépendances entre ces composants, par exemple :
- Un bouton est désactivé lorsqu'un certain champ est vide
- Sélectionner une entrée dans une liste de choix peut changer le contenu d'un champ dans une zone de texte
- Taper du texte dans un champ peut automatiquement sélectionner une ou plusieurs entrées dans une liste
Le médiateur peut éviter ces problèmes en encapsulant le comportement collectif dans un objet séparé.
5. Stratégie
Le patron Stratégie définit une famille d'algorithmes, encapsule chacun d'eux et les rend interchangeables. Il permet à l'algorithme de varier indépendamment des clients qui les utilisent.
Exemples d'utilisation :
- Plusieurs algorithmes existent pour couper un flot de texte en lignes
- Programme qui affiche un calendrier selon l'utilisateur
6. Pont
Le patron Pont sépare et découple une abstraction et son implémentation, permettant à chacune d'évoluer indépendamment. Il est utilisé pour découpler l'interface de l'implémentation.
Exemples d'utilisation :
- Affichage de fenêtres graphiques spécifiques à des plateformes comme XWindow et PMWindow
7. Adaptateur
Le patron Adaptateur permet de convertir l'interface d'une classe en une autre interface que le client attend. Il fait fonctionner des classes qui n'auraient pas pu fonctionner ensemble sans lui, en raison d'une incompatibilité d'interfaces.
Exemple d'utilisation :
- Implémentation de l'interface MouseListener en Java sans implémenter toutes les méthodes. On peut utiliser MouseAdapter qui fournit un comportement par défaut vide pour toutes les méthodes de MouseListener.
Exemple de code en Java avec MouseAdapter :
public class MouseBeeper extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
Toolkit.getDefaultToolkit().beep();
}
}
8. Visiteur
Le patron Visiteur permet de découpler les classes et les traitements, afin d'ajouter de nouveaux traitements sans ajouter de nouvelles méthodes aux classes existantes.
Exemple d'utilisation :
- Affichage d'un arbre de nœuds (les composants d'une voiture). Au lieu de créer des méthodes d'affichage pour chaque sous-classe (Wheel, Engine, Body, Car), une seule classe est créée (CarElementPrintVisitor) pour afficher les éléments.
Exemple de code en Java :
interface CarElementVisitor {
void visit(Wheel wheel);
void visit(Engine engine);
void visit(Body body);
void visitCar(Car car);
}
interface CarElement {
void accept(CarElementVisitor visitor);
}
class Wheel implements CarElement {
private String name;
Wheel(String name) {
this.name = name;
}
String getName() {
return this.name;
}
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Engine implements CarElement {
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Body implements CarElement {
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Car {
CarElement[] elements;
public CarElement[] getElements() {
return elements.clone();
}
public Car() {
this.elements = new CarElement[] {
new Wheel("front left"),
new Wheel("front right"),
new Wheel("back left"),
new Wheel("back right"),
new Body(),
new Engine()
};
}
}
class CarElementPrintVisitor implements CarElementVisitor {
public void visit(Wheel wheel) {
System.out.println("Visiting " + wheel.getName() + " wheel");
}
public void visit(Engine engine) {
System.out.println("Visiting engine");
}
public void visit(Body body) {
System.out.println("Visiting body");
}
public void visitCar(Car car) {
System.out.println("\nVisiting car");
for(CarElement element : car.getElements()) {
element.accept(this);
}
System.out.println("Visited car");
}
}
class CarElementDoVisitor implements CarElementVisitor {
public void visit(Wheel wheel) {
System.out.println("Kicking my " + wheel.getName());
}
public void visit(Engine engine) {
System.out.println("Starting my engine");
}
public void visit(Body body) {
System.out.println("Moving my body");
}
public void visitCar(Car car) {
System.out.println("\nStarting my car");
for(CarElement carElement : car.getElements()) {
carElement.accept(this);
}
System.out.println("Started car");
}
}
public class VisitorDemo {
static public void main(String[] args) {
Car car = new Car();
CarElementVisitor printVisitor = new CarElementPrintVisitor();
CarElementVisitor doVisitor = new CarElementDoVisitor();
printVisitor.visitCar(car);
doVisitor.visitCar(car);
}
}
Exercices
Exercice : Patron Composite
Une figure simple peut être un point, une ligne ou un cercle. Une figure peut être composée d'autres figures, simples ou elles-mêmes composées d'autres figures. Toutes les figures peuvent être dessinées ou translatées.
Question : Utilisez le patron Composite pour construire un diagramme de classe rendant compte de cette hiérarchie d'objets.
Exercice : Patron Composite
Une expression arithmétique peut se représenter de manière arborescente. Par exemple, l'expression (2+3)*4 peut se représenter comme le résultat de l'opération * appliquée à 4 et au résultat d'une seconde opération + appliquée à 2 et à 3.
Question : Utilisez le patron Composite pour produire un diagramme de classes adéquat pour la représentation des expressions arithmétiques.
Exercice : Patron Adaptateur
Un éditeur de jeux développe un jeu éducatif pour les enfants sur les animaux. Les enfants peuvent apprendre la forme et le cri des animaux, notamment le chat et la vache.
Le chat est modélisé par la classe LeChat avec les méthodes formeChat() et criChat(), et la vache par la classe LaVache avec les méthodes criVache() et formeVache().
L'éditeur souhaite améliorer ce jeu en créant une interface commune à tous les animaux, pour en ajouter de nouveaux sans modifier le code client et utiliser le polymorphisme.
Question : Proposez une modélisation des classes pour cette nouvelle version du jeu en utilisant le patron Adaptateur pour réutiliser le code existant.
Exercice : Patron Visiteur
Une figure géométrique peut être un cercle ou un rectangle. On souhaite proposer un outil de dessin qui s'adapte à différents environnements informatiques, où les interfaces et la qualité des dessins dépendent de l'environnement.
Question : En utilisant le patron Visiteur, proposez une modélisation qui isole le dessin d'une figure géométrique et le spécialise en fonction de l'environnement.
FAQ
Qu'est-ce qu'un patron de conception ?
Un patron de conception est une solution réutilisable à un problème courant dans le développement de logiciels, formalisée pour faciliter la communication et l'application de bonnes pratiques.
Quels sont les trois types principaux de patrons de conception ?
Les patrons de conception sont classés en trois catégories : création, structure et comportement.
Pourquoi utiliser le patron Visiteur ?
Le patron Visiteur est utile pour ajouter de nouveaux traitements aux classes existantes sans modifier ces classes, ce qui facilite l'extensibilité du code.