Strategy Pattern : transformer 200 lignes de if/else en code propre
Chaque nouvelle règle métier dans votre appli impose un if en plus. La feature avance, mais le code devient moins clair et moins stable.
Ça peut vite devenir la jungle, et dans certains projets, le Strategy Pattern m'a aidé à remettre de l'ordre dans mon code. Ce pattern permet de séparer les différentes variantes d'un comportement, sans toucher à la logique principale. Au lieu d'ajouter un if, on crée une stratégie qui encapsule ce comportement. Le code qui l'utilise, lui, ne change jamais.
C'est ce qui le rend très utile dans les systèmes qui évoluent souvent : un processeur de paiement avec plusieurs modes, un moteur de calcul avec plusieurs formules, un service de notification avec plusieurs canaux.
Le cas réel : des PDF contractuels en assurance
J'utilise le Strategy Pattern en ce moment sur une application dans le domaine des assurances. Chaque contrat doit générer un document PDF contractuel sur mesure, selon le produit et l'assureur concerné : les modèles et les informations à afficher sont différents selon les types de produits. Plutôt que de multiplier les conditions, une stratégie choisit automatiquement la bonne logique de génération selon le contexte.
1. L'interface : le contrat commun
// Interface que toutes les stratégies doivent implémenter
public interface IBulletinPdfStrategy
{
// Initialise la stratégie avec les données du devis
Task Initialize(Guid devisId);
// Construit la requête JSON spécifique au produit
Task<string> BuildRequest();
}
2. La Factory : un seul switch centralisé
public class BulletinPdfStrategyFactory
{
private readonly IServiceProvider _serviceProvider;
public async Task<IBulletinPdfStrategy> GetStrategy(string type, Guid devisId)
{
IBulletinPdfStrategy strategy;
switch (type)
{
case Codes.Produits.CHO:
strategy = _serviceProvider.GetRequiredService<BulletinPdfCHOStrategy>();
break;
case Codes.Produits.CBNO:
strategy = _serviceProvider.GetRequiredService<BulletinPdfCbnoStrategy>();
break;
// ... autres produits
default:
throw new ArgumentException($"Produit non supporté : {type}");
}
await strategy.Initialize(devisId);
return strategy;
}
}
Un switch centralisé remplace tous les if/else dispersés. La Factory initialise et retourne la stratégie prête à l'emploi.
3. La classe de base : la logique commune
public abstract class BaseBulletinPdfStrategy : IBulletinPdfStrategy
{
// Dépendances communes (repositories, managers...)
protected readonly ITarificationsManager _tarificationsManager;
protected readonly IContratsRepository _contratsRepository;
// Données partagées récupérées lors de l'initialisation
protected Contrat _contrat;
protected ContratPdfTarificationDto _tarification;
public async Task Initialize(Guid devisId)
{
_contrat = await _contratsRepository.GetContratByDevisIdOptim(devisId);
var tarificationDetails = await _tarificationsManager.CalculateTarification(_contrat);
_tarification = tarificationDetails.ToContratTarificationDto();
}
// Méthode abstraite : chaque stratégie implémente sa propre logique
public abstract Task<string> BuildRequest();
}
4. Une stratégie concrète
public class BulletinPdfCbnoStrategy : BaseBulletinPdfStrategy
{
// Dépendances spécifiques à CBNO uniquement
private readonly ICbnoRepository _cbnoRepository;
public override async Task<string> BuildRequest()
{
if (_contrat == null)
throw new InvalidOperationException("Stratégie non initialisée");
var bulletinData = new SimulationCbnoDataDto
{
DateDebutGarantie = _contrat.DateEffet?.ToString("dd/MM/yyyy"),
Souscripteur = await GetSouscripteurInfoAsync(_contrat.Id),
// ... autres données spécifiques CBNO
};
return JsonSerializer.Serialize(bulletinData);
}
}
5. Le service principal : plus aucun if/else
private async Task<Stream> GenerateContractPdfFromApi(Contrat contrat, Guid devisId)
{
var code = contrat.Produit.Code;
// La factory choisit automatiquement la bonne stratégie
var strategy = await _bulletinPdfStrategyFactory.GetStrategy(code, devisId);
var dataContratForApi = await strategy.BuildRequest();
var response = await _generatePdfAppService.GenerateBulletinPdf(code, dataContratForApi);
return await response.Content.ReadAsStreamAsync();
}
Le code reste identique quel que soit le produit. Ajouter un nouveau produit ? Créer une stratégie et l'enregistrer dans la Factory. Ce code ne sera jamais modifié.
À retenir : avant, des if/else partout dans le code métier. Après, une Factory qui décide, des stratégies qui exécutent. Attention quand même à l'over-engineering : pour 2 variantes simples, un if/else suffit largement.
Note : les exemples sont simplifiés pour la clarté (noms modifiés, constructeurs et dépendances omis). Le focus est mis sur la structure du pattern.
Cet article est tiré d'une de mes publications LinkedIn : rejoignez la discussion ↗