Les design patterns les plus courants
8 patterns que tu croises vraiment en pratique — quand les utiliser, quand ils ne sont que du sur-engineering.
Préfère la composition à l'héritage. N'introduis un pattern que si le problème concret s'est déjà présenté au moins deux fois — pas de pattern posé par anticipation, ça s'appelle du sur-engineering.
Singleton / créational
Une seule instance, accès global.
Choisir si : besoin garanti d'une instance unique partagée (config, connexion, cache en mémoire) et un point d'accès global assumé.
Éviter si : la plupart des cas → couplage global, tests difficiles à isoler ; une dépendance injectée fait souvent mieux.
Factory Method / créational
Déléguer la construction, pas le choix du client.
Choisir si : le type exact à instancier dépend du contexte ou de la config, et le code appelant ne doit pas connaître les classes concrètes.
Éviter si : une seule implémentation existe et n'en aura pas d'autre → une fonction ou un constructeur direct suffit.
Strategy / comportemental
Algorithmes interchangeables derrière une interface commune.
Choisir si : plusieurs variantes du même comportement (tri, tarification, validation) doivent être choisies et changées à la volée.
Éviter si : une seule variante existe et restera stable → un if/switch direct est plus lisible qu'une hiérarchie de classes.
Observer / comportemental
Notifier des abonnés sans les connaître.
Choisir si : plusieurs parties du système doivent réagir à un changement d'état sans être couplées à sa source (événements UI, pub/sub interne).
Éviter si : un seul abonné fixe et connu → un appel direct est plus simple à tracer qu'une notification indirecte.
Decorator / structurel
Ajouter du comportement par empilement, pas par héritage.
Choisir si : des responsabilités additionnelles (logging, cache, validation) doivent se combiner librement autour d'un objet existant.
Éviter si : une seule combinaison de comportements suffira toujours → l'empilement de wrappers nuit à la lisibilité pour rien.
Adapter / structurel
Faire parler deux interfaces incompatibles.
Choisir si : intégrer une lib externe ou un legacy dont l'interface ne correspond pas à celle attendue par le reste du code.
Éviter si : tu contrôles les deux côtés de l'interface → aligne-les directement plutôt que d'ajouter une couche.
Repository / accès aux données
Isoler le domaine de la persistance.
Choisir si : le domaine ne doit pas dépendre du moteur de stockage (SQL, API, fichier) et tu veux pouvoir le mocker en test.
Éviter si : app CRUD simple avec un seul accès direct à la DB → la couche ajoute de l'indirection sans bénéfice réel.
Dependency Injection / architectural
Recevoir ses dépendances, ne pas les construire.
Choisir si : un composant a des dépendances qui varient selon le contexte (test, environnement) ou qui doivent rester remplaçables.
Éviter si : petit script sans variation de dépendances → instancier directement évite un conteneur DI superflu.