🎯 OBJECTIF
Comprendre comment :
if/else extensibles par encapsulation d'algorithmes interchangeablesList<Strategy> → Map<Type, Strategy>)🧠 MODÈLE MENTAL
Le problème fondamental : un service qui connaît toutes ses implémentations via if/else viole le principe Open/Closed — ajouter un nouveau cas exige de modifier le service, risquant de casser l'existant. Chaque branche ajoute du couplage et de la complexité.
Strategy retourne le problème : le service dépend d'une interface (abstraction), pas des implémentations. Chaque algorithme devient une classe indépendante qui implémente cette interface. Ajouter un nouveau cas = ajouter une nouvelle classe, sans toucher au service. Le service ne sait pas comment ça marche — il délègue.
En Spring Boot, l'injection de List<Strategy> est le pattern de référence : Spring collecte automatiquement tous les beans qui implémentent l'interface, et le service les transforme en Map<Type, Strategy> pour un routing en O(1) sans condition.
// ❌ Anti-pattern — violation Open/Closed
public class PaymentService {
public void pay(String type, double amount) {
if (type.equals("CARD")) {
// logique carte
} else if (type.equals("PAYPAL")) {
// logique PayPal
} else if (type.equals("CRYPTO")) {
// logique crypto
}
// Ajouter APPLE_PAY → modifier ce service, risquer de casser CARD et PAYPAL
}
}javaProblèmes : violation Open/Closed, couplage fort, difficile à tester isolément, chaque ajout augmente la complexité cyclomatique.
// 1. Interface — le contrat
public interface PaymentStrategy {
void pay(double amount);
}
// 2. Implémentations concrètes — chacune indépendante
public class CardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
// logique carte uniquement
}
}
public class PaypalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
// logique PayPal uniquement
}
}
// 3. Context — dépend de l'interface, pas des implémentations
public class PaymentService {
private PaymentStrategy strategy;
public PaymentService(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void pay(double amount) {
strategy.pay(amount); // délégation pure
}
}javapublic enum PaymentType { CARD, PAYPAL, CRYPTO }
public interface PaymentStrategy {
PaymentType getType(); // identifiant pour le routing
void pay(double amount);
}
@Component
public class CardPaymentStrategy implements PaymentStrategy {
@Override
public PaymentType getType() { return PaymentType.CARD; }
@Override
public void pay(double amount) { /* logique carte */ }
}java@Service
public class PaymentService {
private final Map<PaymentType, PaymentStrategy> strategies;
// ✓ Spring injecte TOUS les beans PaymentStrategy automatiquement
public PaymentService(List<PaymentStrategy> strategies) {
this.strategies = strategies.stream()
.collect(Collectors.toMap(
PaymentStrategy::getType,
Function.identity()
));
}
public void pay(PaymentType type, double amount) {
PaymentStrategy strategy = strategies.get(type); // ✓ O(1), aucun if
if (strategy == null) throw new IllegalArgumentException("Unknown type: " + type);
strategy.pay(amount);
}
}javaAjouter ApplePayStrategy @Component → disponible automatiquement. Zéro modification de PaymentService.
Pipeline (Chain of Responsibility) — quand on veut enchaîner plusieurs traitements au lieu d'en choisir un :
// Spring injecte toutes les validations
@Service
public class OrderValidationService {
private final List<ValidationStrategy> validations;
public void validate(Order order) {
for (ValidationStrategy v : validations) {
v.validate(order); // ✓ toutes s'exécutent dans l'ordre
}
}
}javaRules engine (auto-sélection via supports()) — quand chaque stratégie décide elle-même si elle s'applique :
public interface DiscountStrategy {
boolean supports(Customer customer); // auto-sélection
BigDecimal applyDiscount(BigDecimal price);
}
@Service
public class DiscountService {
private final List<DiscountStrategy> strategies;
public BigDecimal apply(Customer customer, BigDecimal price) {
return strategies.stream()
.filter(s -> s.supports(customer))
.findFirst()
.map(s -> s.applyDiscount(price))
.orElseThrow(() -> new IllegalStateException("No strategy found"));
}
}javaLambda (logique simple) :
@FunctionalInterface
public interface PricingStrategy {
double compute(double price);
}
PricingStrategy discount = price -> price * 0.9;
PricingStrategy premium = price -> price * 0.8;javaEvent dispatcher (pattern event-driven) :
@Service
public class EventDispatcher {
private final Map<String, EventHandler> handlers;
public EventDispatcher(List<EventHandler> handlers) {
this.handlers = handlers.stream()
.collect(Collectors.toMap(EventHandler::eventType, Function.identity()));
}
public void dispatch(Event event) {
Optional.ofNullable(handlers.get(event.getType()))
.orElseThrow(() -> new IllegalArgumentException("No handler for: " + event.getType()))
.handle(event);
}
}javaUtiliser Strategy si :
Ne pas utiliser si :
if reste plus lisible⚡ TL;DR — chaque concept en une ligne
Strategy Pattern
✓ Encapsule un algorithme dans une classe interchangeable — le Context dépend d'une interface, pas des implémentations.
⚠ Sur-engineering pour 2 cas simples stables — un if est parfois plus lisible qu'une hiérarchie de classes.
Pattern Spring Boot (List → Map)
✓ Spring collecte automatiquement tous les @Component qui implémentent l'interface — routing en O(1), zéro if.
⚠ Ajouter une stratégie sans enum correspondant → exception à l'exécution sans erreur de compilation.
Pipeline vs routing ✓ Map de stratégies = choisir une stratégie. List de stratégies = enchaîner toutes les stratégies. ⚠ Confondre les deux mène à des bugs subtils (toutes les stratégies s'appliquent quand une seule devrait).
🎓 À retenir
List<Strategy> injectée par Spring respecte @Order — si l'ordre d'exécution des validations ou des règles compte, annoter chaque bean avec @Order(n). Sans annotation, l'ordre n'est pas garanti entre les instances.Map<Type, Strategy> évite les erreurs de chaîne et offre une validation à la compilation. Une stratégie sans enum correspondant devient visible immédiatement.new CardPaymentStrategy(). Le service se teste avec une implémentation fake. Aucun mock complexe nécessaire.List<T> dans SpringList<T> et Map<String, T> dans Spring