Patrons de conception en Java : de la théorie à la pratique
Les patrons de conception (Design Patterns) sont des solutions éprouvées aux problèmes typiques rencontrés lors du développement de logiciels. Pour un développeur Java, la connaissance des patrons n'est pas simplement un avantage, mais une nécessité. Ils permettent d'écrire un code flexible, maintenable et évolutif. Dans cet article, nous allons analyser quatre patrons clés du catalogue GoF (Gang of Four) avec des exemples concrets en Java.
Pourquoi les patrons sont-ils importants pour un développeur Java ?
Java est un langage orienté objet fortement typé où les patrons aident à :
- Réduire le couplage du code — les classes dépendent d'abstractions, et non d'implémentations concrètes.
- Améliorer la réutilisabilité — des solutions prêtes à l'emploi s'adaptent facilement à de nouvelles tâches.
- Simplifier le travail en équipe — les patrons constituent un langage commun pour les développeurs.
1. Singleton
Le patron Singleton garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à celle-ci. En Java, l'implémentation classique ressemble à ceci :
public class DatabaseConnection { private static volatile DatabaseConnection instance; private String connectionString;
private DatabaseConnection(String connectionString) { this.connectionString = connectionString; }
public static DatabaseConnection getInstance(String connectionString) { if (instance == null) { synchronized (DatabaseConnection.class) { if (instance == null) { instance = new DatabaseConnection(connectionString); } } } return instance; }
public void connect() { System.out.println("Connected to: " + connectionString); }}Quand utiliser Singleton ?
- Gestion de la connexion à une base de données.
- Journaux (Loggers) et gestionnaires de configuration.
- Pool de threads (Thread Pool).
Important : Dans les applications Java modernes, Singleton est souvent remplacé par l'injection de dépendances (conteneurs DI, Spring).
2. Factory Method (Méthode Fabrique)
Factory Method définit une interface pour créer un objet, mais permet aux sous-classes de décider quelle classe instancier. Considérons un exemple de création de notifications :
// Interface produitinterface Notification { void send(String message);}
// Produits concretsclass EmailNotification implements Notification { @Override public void send(String message) { System.out.println("Sending Email: " + message); }}
class SMSNotification implements Notification { @Override public void send(String message) { System.out.println("Sending SMS: " + message); }}
// Fabriqueabstract class NotificationFactory { public abstract Notification createNotification();
public void notifyUser(String message) { Notification notification = createNotification(); notification.send(message); }}
class EmailNotificationFactory extends NotificationFactory { @Override public Notification createNotification() { return new EmailNotification(); }}
class SMSNotificationFactory extends NotificationFactory { @Override public Notification createNotification() { return new SMSNotification(); }}
// Utilisationpublic class Main { public static void main(String[] args) { NotificationFactory factory = new EmailNotificationFactory(); factory.notifyUser("Hello, User!"); }}Avantages de Factory Method :
- Le code ne dépend pas de classes concrètes.
- L'ajout de nouveaux types de notifications ne nécessite pas de modifier le code existant (principe Open/Closed).
- Simplifie les tests unitaires.