Article original : How to Use Design Patterns in Java with Spring Boot – Explained with Code Examples
À mesure que les projets logiciels grandissent, il devient de plus en plus important de garder votre code organisé, maintenable et évolutif. C'est là que les design patterns entrent en jeu. Les design patterns fournissent des solutions éprouvées et réutilisables aux défis courants de la conception logicielle, rendant votre code plus efficace et plus facile à gérer.
Dans ce guide, nous allons plonger profondément dans certains des design patterns les plus populaires et vous montrer comment les implémenter dans Spring Boot. À la fin, vous comprendrez non seulement ces patterns conceptuellement, mais vous serez également capable de les appliquer dans vos propres projets avec confiance.
Table des matières
Introduction aux Design Patterns
Les design patterns sont des solutions réutilisables à des problèmes courants de conception logicielle. Considérez-les comme des meilleures pratiques distillées en modèles qui peuvent être appliqués pour résoudre des défis spécifiques dans votre code. Ils ne sont pas spécifiques à un langage, mais ils peuvent être particulièrement puissants en Java grâce à sa nature orientée objet.
Dans ce guide, nous couvrirons :
Singleton Pattern : Assurer qu'une classe n'a qu'une seule instance.
Factory Pattern : Créer des objets sans spécifier la classe exacte.
Strategy Pattern : Permettre aux algorithmes d'être sélectionnés à l'exécution.
Observer Pattern : Mettre en place une relation de publication-abonnement.
Nous ne couvrirons pas seulement comment ces patterns fonctionnent, mais nous explorerons également comment ils peuvent être appliqués dans Spring Boot pour des applications réelles.
Comment installer votre projet Spring Boot
Avant de plonger dans les patterns, installons un projet Spring Boot :
Prérequis
Assurez-vous d'avoir :
Java 11+
Maven
Spring Boot CLI (optionnel)
Postman ou curl (pour les tests)
Initialisation du projet
Vous pouvez rapidement créer un projet Spring Boot en utilisant Spring Initializr :
curl https://start.spring.io/starter.zip \
-d dependencies=web \
-d name=DesignPatternsDemo \
-d javaVersion=11 -o design-patterns-demo.zip
unzip design-patterns-demo.zip
cd design-patterns-demo
Qu'est-ce que le Singleton Pattern ?
Le Singleton pattern garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à celle-ci. Ce pattern est couramment utilisé pour des services comme la journalisation, la gestion de la configuration ou les connexions à la base de données.
Comment implémenter le Singleton Pattern dans Spring Boot
Les beans Spring Boot sont des singletons par défaut, ce qui signifie que Spring gère automatiquement le cycle de vie de ces beans pour garantir qu'une seule instance existe. Cependant, il est important de comprendre comment le Singleton pattern fonctionne sous le capot, surtout lorsque vous n'utilisez pas de beans gérés par Spring ou que vous avez besoin de plus de contrôle sur la gestion des instances.
Parcourons une implémentation manuelle du Singleton pattern pour démontrer comment vous pouvez contrôler la création d'une seule instance au sein de votre application.
Étape 1 : Créer une classe LoggerService
Dans cet exemple, nous allons créer un service de journalisation simple en utilisant le Singleton pattern. L'objectif est de garantir que toutes les parties de l'application utilisent la même instance de journalisation.
public class LoggerService {
// La variable statique pour contenir la seule instance
private static LoggerService instance;
// Constructeur privé pour empêcher l'instanciation depuis l'extérieur
private LoggerService() {
// Ce constructeur est intentionnellement vide pour empêcher d'autres classes de créer des instances
}
// Méthode publique pour fournir l'accès à la seule instance
public static synchronized LoggerService getInstance() {
if (instance == null) {
instance = new LoggerService();
}
return instance;
}
// Méthode de journalisation exemple
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
Variable statique (
instance) : Elle contient la seule instance deLoggerService.Constructeur privé : Le constructeur est marqué comme privé pour empêcher d'autres classes de créer de nouvelles instances directement.
Méthode
getInstance()synchronisée : La méthode est synchronisée pour la rendre thread-safe, garantissant qu'une seule instance est créée même si plusieurs threads tentent d'y accéder simultanément.Initialisation paresseuse : L'instance est créée uniquement lorsqu'elle est demandée pour la première fois (
initialisation paresseuse), ce qui est efficace en termes d'utilisation de la mémoire.
Utilisation réelle : Ce pattern est couramment utilisé pour les ressources partagées, telles que la journalisation, les paramètres de configuration ou la gestion des connexions à la base de données, où vous souhaitez contrôler l'accès et garantir qu'une seule instance est utilisée dans toute votre application.
Étape 2 : Utiliser le Singleton dans un contrôleur Spring Boot
Maintenant, voyons comment nous pouvons utiliser notre LoggerService Singleton dans un contrôleur Spring Boot. Ce contrôleur exposera un endpoint qui journalise un message chaque fois qu'il est accédé.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogController {
@GetMapping("/log")
public ResponseEntity<String> logMessage() {
// Accéder à l'instance Singleton de LoggerService
LoggerService logger = LoggerService.getInstance();
logger.log("This is a log message!");
return ResponseEntity.ok("Message logged successfully");
}
}
Endpoint GET : Nous avons créé un endpoint
/logqui, lorsqu'il est accédé, journalise un message en utilisant leLoggerService.Utilisation du Singleton : Au lieu de créer une nouvelle instance de
LoggerService, nous appelonsgetInstance()pour garantir que nous utilisons la même instance à chaque fois.Réponse : Après la journalisation, l'endpoint retourne une réponse indiquant le succès.
Étape 3 : Tester le Singleton Pattern
Maintenant, testons cet endpoint en utilisant Postman ou votre navigateur :
GET http://localhost:8080/log
Sortie attendue :
Journal de la console :
[LOG] This is a log message!Réponse HTTP :
Message logged successfully
Vous pouvez appeler l'endpoint plusieurs fois, et vous verrez que la même instance de LoggerService est utilisée, comme indiqué par la sortie de journalisation cohérente.
Cas d'utilisation réels pour le Singleton Pattern
Voici quand vous pourriez vouloir utiliser le Singleton pattern dans des applications réelles :
Gestion de la configuration : Assurez-vous que votre application utilise un ensemble cohérent de paramètres de configuration, surtout lorsque ces paramètres sont chargés à partir de fichiers ou de bases de données.
Pools de connexions à la base de données : Contrôlez l'accès à un nombre limité de connexions à la base de données, en garantissant que le même pool est partagé dans toute l'application.
Mise en cache : Maintenez une seule instance de cache pour éviter des données incohérentes.
Services de journalisation : Comme montré dans cet exemple, utilisez un seul service de journalisation pour centraliser les sorties de journalisation dans différents modules de votre application.
Points clés à retenir
Le Singleton pattern est un moyen facile de garantir qu'une seule instance d'une classe est créée.
La sécurité des threads est cruciale si plusieurs threads accèdent au Singleton, c'est pourquoi nous avons utilisé
synchronizeddans notre exemple.Les beans Spring Boot sont déjà des singletons par défaut, mais comprendre comment l'implémenter manuellement vous aide à gagner plus de contrôle lorsque cela est nécessaire.
Cela couvre l'implémentation et l'utilisation du Singleton pattern. Ensuite, nous explorerons le Factory pattern pour voir comment il peut aider à rationaliser la création d'objets.
Qu'est-ce que le Factory Pattern ?
Le Factory pattern vous permet de créer des objets sans spécifier la classe exacte. Ce pattern est utile lorsque vous avez différents types d'objets qui doivent être instanciés en fonction d'une certaine entrée.
Comment implémenter une Factory dans Spring Boot
Le Factory pattern est incroyablement utile lorsque vous devez créer des objets en fonction de certains critères mais que vous souhaitez découpler le processus de création d'objets de votre logique applicative principale.
Dans cette section, nous allons parcourir la construction d'une NotificationFactory pour envoyer des notifications par e-mail ou SMS. Cela est particulièrement utile si vous prévoyez d'ajouter d'autres types de notifications à l'avenir, tels que des notifications push ou des alertes dans l'application, sans changer votre code existant.
Étape 1 : Créer l'interface Notification
La première étape consiste à définir une interface commune que tous les types de notifications implémenteront. Cela garantit que chaque type de notification (e-mail, SMS, etc.) aura une méthode send() cohérente.
public interface Notification {
void send(String message);
}
Objectif : L'interface
Notificationdéfinit le contrat pour l'envoi de notifications. Toute classe qui implémente cette interface doit fournir une implémentation pour la méthodesend().Évolutivité : En utilisant une interface, vous pouvez facilement étendre votre application à l'avenir pour inclure d'autres types de notifications sans modifier le code existant.
Étape 2 : Implémenter EmailNotification et SMSNotification
Maintenant, implémentons deux classes concrètes, une pour envoyer des e-mails et une autre pour envoyer des messages SMS.
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending Email: " + message);
}
}
public class SMSNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}
Étape 3 : Créer une NotificationFactory
La classe NotificationFactory est responsable de la création d'instances de Notification en fonction du type spécifié. Cette conception garantit que le NotificationController n'a pas besoin de connaître les détails de la création d'objets.
public class NotificationFactory {
public static Notification createNotification(String type) {
switch (type.toUpperCase()) {
case "EMAIL":
return new EmailNotification();
case "SMS":
return new SMSNotification();
default:
throw new IllegalArgumentException("Unknown notification type: " + type);
}
}
}
Méthode de Factory (createNotification()) :
La méthode de factory prend une chaîne (
type) en entrée et retourne une instance de la classe de notification correspondante.Instruction Switch : L'instruction switch sélectionne le type de notification approprié en fonction de l'entrée.
Gestion des erreurs : Si le type fourni n'est pas reconnu, elle lance une
IllegalArgumentException. Cela garantit que les types invalides sont détectés tôt.
Pourquoi utiliser une Factory ?
Découplage : Le factory pattern découple la création d'objets de la logique métier. Cela rend votre code plus modulaire et plus facile à maintenir.
Extensibilité : Si vous souhaitez ajouter un nouveau type de notification, vous n'avez besoin de mettre à jour que la factory sans changer la logique du contrôleur.
Étape 4 : Utiliser la Factory dans un contrôleur Spring Boot
Maintenant, mettons tout cela ensemble en créant un contrôleur Spring Boot qui utilise la NotificationFactory pour envoyer des notifications en fonction de la demande de l'utilisateur.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NotificationController {
@GetMapping("/notify")
public ResponseEntity<String> notify(@RequestParam String type, @RequestParam String message) {
try {
// Créer l'objet Notification approprié en utilisant la factory
Notification notification = NotificationFactory.createNotification(type);
notification.send(message);
return ResponseEntity.ok("Notification sent successfully!");
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
Endpoint GET (/notify) :
Le contrôleur expose un endpoint
/notifyqui accepte deux paramètres de requête :type(soit "EMAIL" soit "SMS") etmessage.Il utilise la
NotificationFactorypour créer le type de notification approprié et envoie le message.Gestion des erreurs : Si un type de notification invalide est fourni, le contrôleur attrape l'
IllegalArgumentExceptionet retourne une réponse400 Bad Request.
Étape 5 : Tester le Factory Pattern
Testons maintenant l'endpoint en utilisant Postman ou un navigateur :
Envoyer une notification par e-mail :
GET http://localhost:8080/notify?type=email&message=Hello%20EmailSortie :
Sending Email: Hello EmailEnvoyer une notification par SMS :
GET http://localhost:8080/notify?type=sms&message=Hello%20SMSSortie :
Sending SMS: Hello SMSTest avec un type invalide :
GET http://localhost:8080/notify?type=unknown&message=TestSortie :
Bad Request: Unknown notification type: unknown
Cas d'utilisation réels pour le Factory Pattern
Le Factory pattern est particulièrement utile dans les scénarios où :
Création dynamique d'objets : Lorsque vous devez créer des objets en fonction de l'entrée de l'utilisateur, comme l'envoi de différents types de notifications, la génération de rapports dans divers formats ou la gestion de différentes méthodes de paiement.
Découplage de la création d'objets : En utilisant une factory, vous pouvez garder votre logique métier principale séparée de la création d'objets, rendant votre code plus maintenable.
Évolutivité : Étendez facilement votre application pour supporter de nouveaux types de notifications sans modifier le code existant. Il suffit d'ajouter une nouvelle classe qui implémente l'interface
Notificationet de mettre à jour la factory.
Qu'est-ce que le Strategy Pattern ?
Le Strategy pattern est parfait lorsque vous devez basculer entre plusieurs algorithmes ou comportements de manière dynamique. Il vous permet de définir une famille d'algorithmes, d'encapsuler chacun dans des classes séparées et de les rendre facilement interchangeables à l'exécution. Cela est particulièrement utile pour sélectionner un algorithme en fonction de conditions spécifiques, en gardant votre code propre, modulaire et flexible.
Cas d'utilisation réel : Imaginez un système de commerce électronique qui doit supporter plusieurs options de paiement, comme les cartes de crédit, PayPal ou les virements bancaires. En utilisant le Strategy pattern, vous pouvez facilement ajouter ou modifier des méthodes de paiement sans altérer le code existant. Cette approche garantit que votre application reste évolutive et maintenable lorsque vous introduisez de nouvelles fonctionnalités ou mettez à jour celles existantes.
Nous allons démontrer ce pattern avec un exemple Spring Boot qui gère les paiements en utilisant soit une stratégie de carte de crédit, soit PayPal.
Étape 1 : Définir une interface PaymentStrategy
Nous commençons par créer une interface commune que toutes les stratégies de paiement implémenteront :
public interface PaymentStrategy {
void pay(double amount);
}
L'interface définit un contrat pour toutes les méthodes de paiement, garantissant la cohérence des implémentations.
Étape 2 : Implémenter les stratégies de paiement
Créez des classes concrètes pour les paiements par carte de crédit et PayPal.
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " with Credit Card");
}
}
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " via PayPal");
}
}
Chaque classe implémente la méthode pay() avec son comportement spécifique.
Étape 3 : Utiliser la stratégie dans un contrôleur
Créez un contrôleur pour sélectionner dynamiquement une stratégie de paiement en fonction de l'entrée de l'utilisateur :
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
@GetMapping("/pay")
public ResponseEntity<String> processPayment(@RequestParam String method, @RequestParam double amount) {
PaymentStrategy strategy = selectPaymentStrategy(method);
if (strategy == null) {
return ResponseEntity.badRequest().body("Invalid payment method");
}
strategy.pay(amount);
return ResponseEntity.ok("Payment processed successfully!");
}
private PaymentStrategy selectPaymentStrategy(String method) {
switch (method.toUpperCase()) {
case "CREDIT": return new CreditCardPayment();
case "PAYPAL": return new PayPalPayment();
default: return null;
}
}
}
L'endpoint accepte method et amount comme paramètres de requête et traite le paiement en utilisant la stratégie appropriée.
Étape 4 : Tester l'endpoint
Paiement par carte de crédit :
GET http://localhost:8080/pay?method=credit&amount=100Sortie :
Paid $100.0 with Credit CardPaiement PayPal :
GET http://localhost:8080/pay?method=paypal&amount=50Sortie :
Paid $50.0 via PayPalMéthode invalide :
GET http://localhost:8080/pay?method=bitcoin&amount=25Sortie :
Invalid payment method
Cas d'utilisation pour le Strategy Pattern
Traitement des paiements : Basculer dynamiquement entre différentes passerelles de paiement.
Algorithmes de tri : Choisir la meilleure méthode de tri en fonction de la taille des données.
Exportation de fichiers : Exporter des rapports dans divers formats (PDF, Excel, CSV).
Points clés à retenir
Le Strategy pattern garde votre code modulaire et suit le principe Open/Closed.
L'ajout de nouvelles stratégies est facile—il suffit de créer une nouvelle classe implémentant l'interface
PaymentStrategy.Il est idéal pour les scénarios où vous avez besoin d'une sélection flexible d'algorithmes à l'exécution.
Ensuite, nous explorerons le Observer pattern, parfait pour gérer les architectures pilotées par événements.
Qu'est-ce que le Observer Pattern ?
Le Observer pattern est idéal lorsque vous avez un objet (le sujet) qui doit notifier plusieurs autres objets (observateurs) des changements dans son état. Il est parfait pour les systèmes pilotés par événements où les mises à jour doivent être poussées vers divers composants sans créer de couplage serré entre eux. Ce pattern vous permet de maintenir une architecture propre, surtout lorsque différentes parties de votre système doivent réagir aux changements de manière indépendante.
Cas d'utilisation réel : Ce pattern est couramment utilisé dans les systèmes qui envoient des notifications ou des alertes, comme les applications de chat ou les trackers de prix d'actions, où les mises à jour doivent être poussées vers les utilisateurs en temps réel. En utilisant le Observer pattern, vous pouvez ajouter ou supprimer des types de notifications facilement sans altérer la logique centrale.
Nous allons démontrer comment implémenter ce pattern dans Spring Boot en construisant un système de notification simple où les notifications par e-mail et SMS sont envoyées chaque fois qu'un utilisateur s'inscrit.
Étape 1 : Créer une interface Observer
Nous commençons par définir une interface commune que tous les observateurs implémenteront :
public interface Observer {
void update(String event);
}
L'interface établit un contrat où tous les observateurs doivent implémenter la méthode update(), qui sera déclenchée chaque fois que le sujet change.
Étape 2 : Implémenter EmailObserver et SMSObserver
Ensuite, nous créons deux implémentations concrètes de l'interface Observer pour gérer les notifications par e-mail et SMS.
Classe EmailObserver
public class EmailObserver implements Observer {
@Override
public void update(String event) {
System.out.println("Email sent for event: " + event);
}
}
L'EmailObserver gère l'envoi de notifications par e-mail chaque fois qu'il est notifié d'un événement.
Classe SMSObserver
public class SMSObserver implements Observer {
@Override
public void update(String event) {
System.out.println("SMS sent for event: " + event);
}
}
Le SMSObserver gère l'envoi de notifications par SMS chaque fois qu'il est notifié.
Étape 3 : Créer une classe UserService (Le Sujet)
Nous allons maintenant créer une classe UserService qui agit comme le sujet, notifiant ses observateurs enregistrés chaque fois qu'un utilisateur s'inscrit.
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserService {
private List<Observer> observers = new ArrayList<>();
// Méthode pour enregistrer les observateurs
public void registerObserver(Observer observer) {
observers.add(observer);
}
// Méthode pour notifier tous les observateurs enregistrés d'un événement
public void notifyObservers(String event) {
for (Observer observer : observers) {
observer.update(event);
}
}
// Méthode pour enregistrer un nouvel utilisateur et notifier les observateurs
public void registerUser(String username) {
System.out.println("User registered: " + username);
notifyObservers("User Registration");
}
}
Liste des observateurs : Garde une trace de tous les observateurs enregistrés.
Méthode
registerObserver(): Ajoute de nouveaux observateurs à la liste.Méthode
notifyObservers(): Notifie tous les observateurs enregistrés lorsqu'un événement se produit.Méthode
registerUser(): Enregistre un nouvel utilisateur et déclenche des notifications à tous les observateurs.
Étape 4 : Utiliser le Observer Pattern dans un contrôleur
Enfin, nous allons créer un contrôleur Spring Boot pour exposer un endpoint pour l'inscription des utilisateurs. Ce contrôleur enregistrera à la fois EmailObserver et SMSObserver avec le UserService.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
private final UserService userService;
public UserController() {
this.userService = new UserService();
// Enregistrer les observateurs
userService.registerObserver(new EmailObserver());
userService.registerObserver(new SMSObserver());
}
@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestParam String username) {
userService.registerUser(username);
return ResponseEntity.ok("User registered and notifications sent!");
}
}
Endpoint (
/register) : Accepte un paramètreusernameet enregistre l'utilisateur, déclenchant des notifications à tous les observateurs.Observateurs :
EmailObserveretSMSObserversont tous deux enregistrés avecUserService, donc ils sont notifiés chaque fois qu'un utilisateur s'inscrit.
Tester le Observer Pattern
Maintenant, testons notre implémentation en utilisant Postman ou un navigateur :
POST http://localhost:8080/api/register?username=JohnDoe
Sortie attendue dans la console :
User registered: JohnDoe
Email sent for event: User Registration
SMS sent for event: User Registration
Le système enregistre l'utilisateur et notifie à la fois les observateurs Email et SMS, montrant la flexibilité du Observer pattern.
Applications réelles du Observer Pattern
Systèmes de notification : Envoyer des mises à jour aux utilisateurs via différents canaux (e-mail, SMS, notifications push) lorsque certains événements se produisent.
Architectures pilotées par événements : Notifier plusieurs sous-systèmes lorsque des actions spécifiques ont lieu, telles que les activités des utilisateurs ou les alertes système.
Streaming de données : Diffuser des changements de données à divers consommateurs en temps réel (par exemple, prix des actions en direct ou flux de médias sociaux).
Comment utiliser l'injection de dépendances de Spring Boot
Jusqu'à présent, nous avons créé manuellement des objets pour démontrer les design patterns. Cependant, dans les applications Spring Boot réelles, l'injection de dépendances (DI) est la manière préférée de gérer la création d'objets. DI permet à Spring de gérer automatiquement l'instanciation et le câblage de vos classes, rendant votre code plus modulaire, testable et maintenable.
Refactorisons notre exemple de Strategy pattern pour tirer parti des puissantes capacités de DI de Spring Boot. Cela nous permettra de basculer entre les stratégies de paiement de manière dynamique, en utilisant les annotations de Spring pour gérer les dépendances.
Strategy Pattern mis à jour utilisant le DI de Spring Boot
Dans notre exemple refactorisé, nous allons exploiter les annotations de Spring comme @Component, @Service et @Autowired pour rationaliser le processus d'injection des dépendances.
Étape 1 : Annoter les stratégies de paiement avec @Component
Tout d'abord, nous allons marquer nos implémentations de stratégie avec l'annotation @Component afin que Spring puisse les détecter et les gérer automatiquement.
@Component("creditCardPayment")
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " with Credit Card");
}
}
@Component("payPalPayment")
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal");
}
}
Annotation
@Component: En ajoutant@Component, nous disons à Spring de traiter ces classes comme des beans gérés par Spring. La valeur de la chaîne ("creditCardPayment"et"payPalPayment") agit comme l'identifiant du bean.Flexibilité : Cette configuration nous permet de basculer entre les stratégies en utilisant l'identifiant de bean approprié.
Étape 2 : Refactoriser le PaymentService pour utiliser l'injection de dépendances
Ensuite, modifions le PaymentService pour injecter une stratégie de paiement spécifique en utilisant @Autowired et @Qualifier.
@Service
public class PaymentService {
private final PaymentStrategy paymentStrategy;
@Autowired
public PaymentService(@Qualifier("payPalPayment") PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void processPayment(double amount) {
paymentStrategy.pay(amount);
}
}
Annotation
@Service: MarquePaymentServicecomme un bean de service géré par Spring.@Autowired: Spring injecte automatiquement la dépendance requise.@Qualifier: Spécifie quelle implémentation dePaymentStrategyinjecter. Dans cet exemple, nous utilisons"payPalPayment".Facilité de configuration : En changeant simplement la valeur de
@Qualifier, vous pouvez basculer la stratégie de paiement sans altérer aucune logique métier.
Étape 3 : Utiliser le service refactorisé dans un contrôleur
Pour voir les avantages de ce refactoring, mettons à jour le contrôleur pour utiliser notre PaymentService :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class PaymentController {
private final PaymentService paymentService;
@Autowired
public PaymentController(PaymentService paymentService) {
this.paymentService = paymentService;
}
@GetMapping("/pay")
public String makePayment(@RequestParam double amount) {
paymentService.processPayment(amount);
return "Payment processed using the current strategy!";
}
}
@Autowired: Le contrôleur reçoit automatiquement lePaymentServiceavec la stratégie de paiement injectée.Endpoint GET (
/pay) : Lorsqu'il est accédé, il traite un paiement en utilisant la stratégie actuellement configurée (PayPal dans cet exemple).
Tester le Strategy Pattern refactorisé avec DI
Maintenant, testons la nouvelle implémentation en utilisant Postman ou un navigateur :
GET http://localhost:8080/api/pay?amount=100
Sortie attendue :
Paid $100.0 using PayPal
Si vous changez le qualificateur dans PaymentService en "creditCardPayment", la sortie changera en conséquence :
Paid $100.0 with Credit Card
Avantages de l'utilisation de l'injection de dépendances
Couplage lâche : Le service et le contrôleur n'ont pas besoin de connaître les détails de la manière dont un paiement est traité. Ils s'appuient simplement sur Spring pour injecter la bonne implémentation.
Modularité : Vous pouvez facilement ajouter de nouvelles méthodes de paiement (par exemple,
BankTransferPayment,CryptoPayment) en créant de nouvelles classes annotées avec@Componentet en ajustant le@Qualifier.Configurabilité : En exploitant les profils Spring, vous pouvez basculer entre les stratégies en fonction de l'environnement (par exemple, développement vs. production).
Exemple : Vous pouvez utiliser @Profile pour injecter automatiquement différentes stratégies en fonction du profil actif :
@Component
@Profile("dev")
public class DevPaymentStrategy implements PaymentStrategy { /* ... */ }
@Component
@Profile("prod")
public class ProdPaymentStrategy implements PaymentStrategy { /* ... */ }
Points clés à retenir
En utilisant le DI de Spring Boot, vous pouvez simplifier la création d'objets et améliorer la flexibilité de votre code.
Le Strategy Pattern combiné avec le DI vous permet de basculer facilement entre différentes stratégies sans changer votre logique métier principale.
L'utilisation de
@Qualifieret des profils Spring vous donne la flexibilité de configurer votre application en fonction de différents environnements ou exigences.
Cette approche non seulement rend votre code plus propre, mais le prépare également pour des configurations plus avancées et une mise à l'échelle future. Dans la section suivante, nous explorerons les meilleures pratiques et conseils d'optimisation pour faire passer vos applications Spring Boot au niveau supérieur.
Meilleures pratiques et conseils d'optimisation
Bonnes pratiques générales
Ne pas surutiliser les patterns : Utilisez-les uniquement lorsque cela est nécessaire. La sur-ingénierie peut rendre votre code plus difficile à maintenir.
Privilégier la composition à l'héritage : Les patterns comme Strategy et Observer sont de bons exemples de ce principe.
Garder vos patterns flexibles : Utilisez des interfaces pour garder votre code découplé.
Considérations de performance
Singleton Pattern : Assurez la sécurité des threads en utilisant
synchronizedou leBill Pugh Singleton Design.Factory Pattern : Mettez en cache les objets s'ils sont coûteux à créer.
Observer Pattern : Utilisez le traitement asynchrone si vous avez de nombreux observateurs pour éviter les blocages.
Sujets avancés
Utilisation de la Réflexion avec le Factory pattern pour le chargement dynamique de classes.
Exploitation des Profils Spring pour basculer les stratégies en fonction de l'environnement.
Ajout de la Documentation Swagger pour vos endpoints API.
Conclusion et points clés à retenir
Dans ce tutoriel, nous avons exploré certains des design patterns les plus puissants—Singleton, Factory, Strategy et Observer—et démontré comment les implémenter dans Spring Boot. Résumons brièvement chaque pattern et soulignons à quoi il est le mieux adapté :
Singleton Pattern :
Résumé : Garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à celle-ci.
Meilleur pour : Gérer les ressources partagées comme les paramètres de configuration, les connexions à la base de données ou les services de journalisation. Il est idéal lorsque vous souhaitez contrôler l'accès à une instance partagée dans toute votre application.
Factory Pattern :
Résumé : Fournit un moyen de créer des objets sans spécifier la classe exacte à instancier. Ce pattern découple la création d'objets de la logique métier.
Meilleur pour : Les scénarios où vous devez créer différents types d'objets en fonction des conditions d'entrée, comme l'envoi de notifications par e-mail, SMS ou notifications push. Il est idéal pour rendre votre code plus modulaire et extensible.
Strategy Pattern :
Résumé : Vous permet de définir une famille d'algorithmes, d'encapsuler chacun d'eux et de les rendre interchangeables. Ce pattern vous aide à choisir un algorithme à l'exécution.
Meilleur pour : Lorsque vous devez basculer entre différents comportements ou algorithmes de manière dynamique, comme le traitement de diverses méthodes de paiement dans une application de commerce électronique. Il garde votre code flexible et adhère au principe Open/Closed.
Observer Pattern :
Résumé : Définit une dépendance un-à-plusieurs entre objets de sorte que lorsqu'un objet change d'état, tous ses dépendants sont notifiés automatiquement.
Meilleur pour : Les systèmes pilotés par événements comme les services de notification, les mises à jour en temps réel dans les applications de chat ou les systèmes qui doivent réagir aux changements de données. Il est idéal pour découpler les composants et rendre votre système plus évolutif.
Qu'est-ce qui suit ?
Maintenant que vous avez appris ces design patterns essentiels, essayez de les intégrer dans vos projets existants pour voir comment ils peuvent améliorer la structure et l'évolutivité de votre code. Voici quelques suggestions pour une exploration plus approfondie :
Expérimentez : Essayez d'implémenter d'autres design patterns comme Decorator, Proxy et Builder pour élargir votre boîte à outils.
Pratiquez : Utilisez ces patterns pour refactoriser des projets existants et améliorer leur maintenabilité.
Partagez : Si vous avez des questions ou souhaitez partager votre expérience, n'hésitez pas à demander !
J'espère que ce guide vous a aidé à comprendre comment utiliser efficacement les design patterns en Java. Continuez à expérimenter, et bon codage !