À 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 motifs de conception entrent en jeu. Les motifs de conception fournissent des solutions réutilisables et éprouvées aux défis courants de conception logicielle, rendant votre code plus efficace et plus facile à gérer.
Dans ce guide, nous plongerons profondément dans certains des motifs de conception les plus populaires et vous montrerons comment les implémenter dans Spring Boot. À la fin, vous comprendrez non seulement ces motifs conceptuellement, mais vous serez également capable de les appliquer dans vos propres projets en toute confiance.
Table des matières
Introduction aux Design Patterns
Les design patterns sont des solutions réutilisables aux problèmes communs de conception de logiciels. Pensez à eux 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 en raison de sa nature orientée objet.
Dans ce guide, nous couvrirons :
-
Singleton Pattern : S’assurer qu’une classe a une seule instance.
-
Factory Pattern : Créer des objets sans spécifier la classe exacte.
-
Strategy Pattern : Permettre la sélection d’algorithmes à l’exécution.
-
Observer Pattern : Établir une relation de publication-abonnement.
Nous verrons non seulement comment ces modèles fonctionnent, mais aussi comment ils peuvent être appliqués dans Spring Boot pour des applications du monde réel.
Comment Configurer Votre Projet Spring Boot
Avant d’aborder les modèles, mettons en place 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 modèle Singleton ?
Le modèle Singleton garantit qu’une classe possède une seule instance et fournit un point d’accès global à celle-ci. Ce modèle est couramment utilisé pour des services tels que la journalisation, la gestion de la configuration ou les connexions de base de données.
Comment implémenter le modèle Singleton 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 fonctionne le modèle Singleton sous-jacent, en particulier lorsque vous n’utilisez pas des beans gérés par Spring ou avez besoin de plus de contrôle sur la gestion des instances.
Marchons à travers une implémentation manuelle du modèle Singleton pour démontrer comment vous pouvez contrôler la création d’une seule instance dans votre application.
Étape 1 : Créer une classe LoggerService
Dans cet exemple, nous allons créer un service de journalisation simple en utilisant le modèle Singleton. 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 l'instance unique
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 à l'instance unique
public static synchronized LoggerService getInstance() {
if (instance == null) {
instance = new LoggerService();
}
return instance;
}
// Méthode de journalisation d'exemple
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
-
Variable Statique (
instance
): Ceci contient l’instance unique deLoggerService
. -
Constructeur Privé: Le constructeur est marqué 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 dans le monde réel: Ce modèle est couramment utilisé pour les ressources partagées, telles que le journalisation, les paramètres de configuration ou la gestion des connexions de base de données, où vous souhaitez contrôler l’accès et vous assurer 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 Singleton LoggerService
dans un contrôleur Spring Boot. Ce contrôleur exposera un point de terminaison qui enregistre 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");
}
}
-
Point de terminaison GET: Nous avons créé un point de terminaison
/log
qui, lorsqu’il est accédé, enregistre un message en utilisant leLoggerService
. -
Utilisation du Singleton: Au lieu de créer une nouvelle instance de
LoggerService
, nous appelonsgetInstance()
pour nous assurer que nous utilisons la même instance à chaque fois. -
Réponse: Après la journalisation, le point de terminaison renvoie une réponse indiquant le succès.
Étape 3: Tester le modèle Singleton
Maintenant, testons ce point de terminaison en utilisant Postman ou votre navigateur :
GET http://localhost:8080/log
Sortie Attendue:
-
Journal console :
[LOG] C'est un message de journalisation !
-
Réponse HTTP :
Message journalisé avec succès
Vous pouvez appeler le point de terminaison plusieurs fois, et vous verrez que la même instance de LoggerService
est utilisée, comme indiqué par la sortie de journal cohérente.
Cas d’utilisation du modèle Singleton dans le monde réel
Voici quand vous pourriez vouloir utiliser le modèle Singleton dans les applications du monde réel :
-
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 veillant à ce que le même pool soit partagé dans toute l’application.
-
Mise en cache: Maintenez une seule instance de cache pour éviter les données incohérentes.
-
Services de journalisation: Comme le montre cet exemple, utilisez un seul service de journalisation pour centraliser les sorties de journal à travers différents modules de votre application.
Points clés
-
Le modèle Singleton est un moyen simple 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é
synchronized
dans notre exemple. -
Les beans Spring Boot sont déjà des singletons par défaut, mais comprendre comment les implémenter manuellement vous permet d’avoir plus de contrôle lorsque nécessaire.
Cela couvre l’implémentation et l’utilisation du modèle Singleton. Ensuite, nous explorerons le modèle Factory pour voir comment il peut aider à rationaliser la création d’objets.
Qu’est-ce que le modèle Factory?
Le modèle Factory vous permet de créer des objets sans spécifier la classe exacte. Ce modèle est utile lorsque vous avez différents types d’objets qui doivent être instanciés en fonction de certaines entrées.
Comment implémenter une Factory dans Spring Boot
Le modèle Factory 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 la logique principale de votre application.
Dans cette section, nous allons construire une NotificationFactory
pour envoyer des notifications par e-mail ou SMS. Cela est particulièrement utile si vous prévoyez d’ajouter plus de types de notifications à l’avenir, tels que des notifications push ou des alertes dans l’application, sans modifier 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);
}
-
But: L’interface
Notification
définit le contrat pour l’envoi de notifications. Toute classe qui implémente cette interface doit fournir une implémentation de la méthodesend()
. -
Scalabilité: 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 les classes EmailNotification
et SMSNotification
Maintenant, implémentons deux classes concrètes, une pour l’envoi d’e-mails et une autre pour l’envoi de 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 d’usine (createNotification()
):
-
La méthode d’usine prend une chaîne (
type
) en entrée et renvoie 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, il lance une
IllegalArgumentException
. Cela garantit que les types non valides sont détectés tôt.
Pourquoi utiliser une fabrique ?
-
Découplage: Le modèle de conception de fabrique sépare la création d’objets de la logique métier. Cela rend votre code plus modulaire et plus facile à maintenir.
-
Extensibilité: Si vous voulez ajouter un nouveau type de notification, vous devez seulement mettre à jour la fabrique sans changer la logique du contrôleur.
Étape 4: Utiliser la fabrique dans un contrôleur Spring Boot
Maintenant, mettons tout en place 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 fabrique
Notification notification = NotificationFactory.createNotification(type);
notification.send(message);
return ResponseEntity.ok("Notification sent successfully!");
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
Point de terminaison GET (/notify
):
-
Le contrôleur expose un point de terminaison
/notify
qui accepte deux paramètres de requête :type
(soit « EMAIL » soit « SMS ») etmessage
. -
Il utilise la
NotificationFactory
pour créer le type de notification approprié et envoyer le message. -
Gestion des erreurs : Si un type de notification invalide est fourni, le contrôleur capture l’exception
IllegalArgumentException
et renvoie une réponse400 Bad Request
.
Étape 5 : Tester le modèle de conception Factory
Testons le point de terminaison en utilisant Postman ou un navigateur :
-
Envoyer une notification par e-mail :
GET http://localhost:8080/notify?type=email&message=Hello%20Email
Résultat :
Envoi d'un e-mail : Hello Email
-
Envoyer une notification par SMS:
GET http://localhost:8080/notify?type=sms&message=Hello%20SMS
Sortie:
Envoi d'un SMS : Bonjour SMS
-
Tester avec un type invalide:
GET http://localhost:8080/notify?type=unknown&message=Test
Sortie:
Requête incorrecte : Type de notification inconnu : inconnu
Scénarios d’utilisation du modèle Factory dans le monde réel
Le modèle Factory est particulièrement utile dans des scénarios où :
-
Création dynamique d’objets : Lorsque vous devez créer des objets en fonction de l’entrée utilisateur, comme l’envoi de différents types de notifications, la génération de rapports dans différents formats ou la gestion de différents modes de paiement.
-
Découplage de la création d’objets: En utilisant une fabrique, vous pouvez séparer votre logique métier principale de la création d’objets, rendant votre code plus maintenable.
-
Scalabilité: Étendez facilement votre application pour prendre en charge de nouveaux types de notifications sans modifier le code existant. Il suffit d’ajouter une nouvelle classe qui implémente l’interface
Notification
et de mettre à jour la fabrique.
Qu’est-ce que le Pattern Stratégie?
Le Pattern Stratégie 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 maintenant votre code propre, modulaire et flexible.
Cas d’utilisation réel: Imaginez un système de commerce électronique qui doit prendre en charge plusieurs options de paiement, telles que les cartes de crédit, PayPal ou les virements bancaires. En utilisant le modèle de conception Stratégie, vous pouvez facilement ajouter ou modifier des méthodes de paiement sans modifier le code existant. Cette approche garantit que votre application reste évolutive et maintenable lorsque vous introduisez de nouvelles fonctionnalités ou mettez à jour les existantes.
Nous illustrerons ce modèle avec un exemple Spring Boot qui gère les paiements en utilisant soit une carte de crédit, soit une stratégie 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 entre les 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;
}
}
}
Le point de terminaison accepte méthode
et montant
en tant que paramètres de requête et traite le paiement en utilisant la stratégie appropriée.
Étape 4 : Tester le point de terminaison
-
Paiement par carte de crédit:
GET http://localhost:8080/pay?method=credit&montant=100
Sortie:
Payé $100.0 avec carte de crédit
-
Paiement PayPal:
GET http://localhost:8080/pay?method=paypal&montant=50
Sortie:
Payé $50.0 via PayPal
-
Méthode invalide:
GET http://localhost:8080/pay?method=bitcoin&montant=25
Sortie:
Méthode de paiement invalide
Scénarios d’utilisation pour le patron de conception Strategy
-
Traitement des paiements: Basculer dynamiquement entre différents 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
-
Le modèle de conception Strategy garde votre code modulaire et suit le principe Ouvert/Fermé.
-
Ajouter de nouvelles stratégies est facile : il suffit de créer une nouvelle classe implémentant l’interface
PaymentStrategy
. -
C’est idéal pour les scénarios où vous avez besoin d’une sélection d’algorithme flexible à l’exécution.
Ensuite, nous explorerons le modèle Observer, parfait pour gérer les architectures basées sur des événements.
Qu’est-ce que le modèle Observer?
Le pattern Observer est idéal lorsque vous avez un objet (le sujet) qui doit notifier plusieurs autres objets (observateurs) des changements de 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, tels que des applications de discussion ou des suiveurs de prix d’actions, où les mises à jour doivent être poussées vers les utilisateurs en temps réel. En utilisant le pattern Observer, vous pouvez ajouter ou supprimer facilement des types de notifications sans altérer la logique de base.
Nous allons démontrer comment implémenter ce pattern dans Spring Boot en construisant un système de notification simple où des 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 changement du sujet.
É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);
}
}
Le EmailObserver
gère l’envoi de notifications par e-mail lorsqu’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 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 en tant que 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 des 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.
-
registerObserver()
Méthode : Ajoute de nouveaux observateurs à la liste. -
notifyObservers()
Méthode : Notifie tous les observateurs enregistrés lorsqu’un événement se produit. -
registerUser()
Méthode : Enregistre un nouvel utilisateur et déclenche des notifications à tous les observateurs.
Étape 4 : Utiliser le modèle Observateur dans un Contrôleur
Enfin, nous allons créer un contrôleur Spring Boot pour exposer un point de terminaison pour l’inscription de l’utilisateur. 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!");
}
}
-
Point de terminaison (
/register
): Accepte un paramètreusername
et enregistre l’utilisateur, déclenchant des notifications à tous les observateurs. -
Observateurs: À la fois
EmailObserver
etSMSObserver
sont enregistrés avec leUserService
, donc ils sont notifiés chaque fois qu’un utilisateur s’inscrit.
Tester le motif Observateur
Maintenant, testons notre implémentation à l’aide de Postman ou d’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, mettant en valeur la flexibilité du motif Observateur.
Applications du motif Observateur dans le monde réel
-
Systèmes de notification : Envoi de mises à jour aux utilisateurs via différents canaux (email, SMS, notifications push) lorsque certains événements se produisent.
-
Architectures orientées événements: Notification de plusieurs sous-systèmes lorsque des actions spécifiques se produisent, telles que des activités utilisateur ou des alertes système.
-
Diffusion de données: Diffusion des changements de données à différents consommateurs en temps réel (par exemple, les prix des actions en direct ou les 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 illustrer les modèles de conception. Cependant, dans les applications Spring Boot du monde réel, l’injection de dépendances (DI) est la méthode préférée pour gérer la création d’objets. La 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 modèle de stratégie pour tirer parti des puissantes capacités de DI de Spring Boot. Cela nous permettra de basculer dynamiquement entre les stratégies de paiement, en utilisant les annotations de Spring pour gérer les dépendances.
Modèle de stratégie mis à jour utilisant l’injection de dépendances de Spring Boot
Dans notre exemple refactorisé, nous utiliserons les annotations de Spring telles que @Component
, @Service
et @Autowired
pour simplifier le processus d’injection de dépendances.
Étape 1 : Annoter les stratégies de paiement avec @Component
Tout d’abord, nous marquerons 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");
}
}
-
@Component
Annotation : En ajoutant@Component
, nous indiquons à Spring de traiter ces classes comme des beans gérés par Spring. La valeur de la chaîne ("creditCardPayment"
et"payPalPayment"
) agit comme identifiant du bean. -
Flexibilité : Cette configuration nous permet de passer d’une stratégie à une autre 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
: MarquePaymentService
comme un bean de service géré par Spring. -
@Autowired
: Spring injecte automatiquement la dépendance requise. -
@Qualifier
: Spécifie quelle implémentation dePaymentStrategy
injecter. Dans cet exemple, nous utilisons"payPalPayment"
. -
Simplicité de configuration : En modifiant simplement la valeur de
@Qualifier
, vous pouvez changer la stratégie de paiement sans modifier la logique métier.
Étape 3 : Utilisation du 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 lePaymentService
avec la stratégie de paiement injectée. -
Point de terminaison 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 Pattern de Stratégie Refactorisé avec DI
À présent, testons la nouvelle implémentation en utilisant Postman ou un navigateur :
GET http://localhost:8080/api/pay?amount=100
Résultat Attendu:
Paid $100.0 using PayPal
Si vous changez le qualificatif dans PaymentService
en "creditCardPayment"
, le résultat 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 sur la façon dont un paiement est traité. Ils se reposent 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@Component
et en ajustant le@Qualifier
. - Configurabilité: En tirant parti des Profils Spring, vous pouvez changer de 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
-
En utilisant l’injection de dépendances de Spring Boot, vous pouvez simplifier la création d’objets et améliorer la flexibilité de votre code.
-
Le pattern Stratégie combiné avec l’injection de dépendances vous permet de basculer facilement entre différentes stratégies sans modifier la logique métier principale.
-
En utilisant
@Qualifier
et les profils Spring, vous avez la flexibilité de configurer votre application en fonction des différents environnements ou des besoins.
Cette approche rend non seulement votre code plus propre, mais le prépare également à 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 amener vos applications Spring Boot au niveau supérieur.
Meilleures Pratiques et Conseils d’Optimisation
Meilleures pratiques générales
-
Ne pas abuser des motifs : Utilisez-les uniquement lorsque nécessaire. Un excès d’ingénierie peut rendre votre code plus difficile à maintenir.
-
Favoriser la composition à l’héritage : Des motifs comme Strategy et Observer sont de grands exemples de ce principe.
-
Gardez vos motifs flexibles : Utilisez des interfaces pour garder votre code découplé.
Considérations de performance
-
Motif Singleton : Assurez la sécurité des threads en utilisant
synchronized
ou leBill Pugh Singleton Design
. -
Motif Fabrique : Mettez en cache les objets s’ils sont coûteux à créer.
-
Motif Observateur : Utilisez un traitement asynchrone si vous avez de nombreux observateurs pour éviter le blocage.
Thèmes avancés
-
Utiliser la Réflexion avec le modèle de Fabrique pour le chargement dynamique de classes.
-
Exploiter les Profils Spring pour changer de stratégies en fonction de l’environnement.
-
Ajouter la Documentation Swagger pour vos points de terminaison API.
Conclusion et Points Clés
Dans ce tutoriel, nous avons exploré certains des modèles de conception les plus puissants—Singleton, Fabrique, Stratégie et Observateur—et démontré comment les mettre en œuvre dans Spring Boot. Résumons brièvement chaque modèle et soulignons à quoi il est le mieux adapté :
Modèle Singleton:
-
Résumé: Assure qu’une classe n’a qu’une seule instance et fournit un point d’accès global à celle-ci.
-
Idéal Pour: Gérer des ressources partagées comme les paramètres de configuration, les connexions à la base de données ou les services de journalisation. C’est idéal lorsque vous souhaitez contrôler l’accès à une instance partagée dans l’ensemble de votre application.
Modèle d’usine:
-
Résumé: Fournit un moyen de créer des objets sans spécifier la classe exacte à instancier. Ce modèle découple la création d’objets de la logique métier.
-
Idéal Pour: Scénarios où vous devez créer différents types d’objets en fonction de conditions d’entrée, telles que l’envoi de notifications par e-mail, SMS ou notifications push. C’est idéal pour rendre votre code plus modulaire et extensible.
Modèle de stratégie:
-
Résumé: Vous permet de définir une famille d’algorithmes, d’encapsuler chacun d’eux et de les rendre interchangeables. Ce modèle vous aide à choisir un algorithme à l’exécution.
-
Idéal Pour: Lorsque vous devez basculer dynamiquement entre différents comportements ou algorithmes, tels que le traitement de divers modes de paiement dans une application de commerce électronique. Cela rend votre code flexible et respecte le Principe Ouvert/Fermé.
Modèle Observateur:
-
Résumé : Définit une dépendance un-à-plusieurs entre les objets afin que lorsqu’un objet change d’état, tous ses dépendants soient automatiquement notifiés.
-
Meilleur pour : Systèmes déclenchés par des é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. C’est idéal pour découpler les composants et rendre votre système plus évolutif.
Et après ?
Maintenant que vous avez appris ces motifs de conception 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 approfondir vos explorations :
-
Expérimentez : Essayez d’implémenter d’autres motifs de conception comme Décorateur, Proxy, et Constructeur pour élargir votre boîte à outils.
-
Pratique: Utilisez ces motifs pour refactoriser des projets existants et améliorer leur maintenabilité.
-
Partage: Si vous avez des questions ou si vous souhaitez partager votre expérience, n’hésitez pas à nous contacter!
J’espère que ce guide vous a aidé à comprendre comment utiliser efficacement les motifs de conception en Java. Continuez à expérimenter et joyeux codage!
Source:
https://www.freecodecamp.org/news/how-to-use-design-patterns-in-java-with-spring-boot/