A propos de moi
Architecte logiciel avec 20 ans d’expérience en développement d’applications Web (Java Spring, Angular, et AWS). Formateur passionné et Scrum Master, actuellement chez Médiamétrie, j’aide les développeurs à améliorer la qualité et la sécurité des applications du système d’informations, j’accompagne la transformation et favorise l’amélioration continue.
- Expertise en architecture logicielle et AWS
- Formation et support en développement logiciel
- Gouvernance technologique et transformation
Mon expertise
Architecte Logiciel
Conception et supervision de solutions sécurisées et évolutives. Expertise en gouvernance technologique, transformation d'entreprise, et sécurisation des systèmes d'information.
Développeur Full Stack
Maîtrise complète du développement, de la création d'applications backend robustes avec Java Spring à la conception d'interfaces dynamiques en Angular.
Formateur
Passionné par la transmission de savoir, j'accompagne les développeurs dans leur montée en compétences en Java Spring et meilleures pratiques de développement.
Quelques sociétés dans lesquelles j'ai donné des formations
Mes derniers articles

DRY, KISS, YAGNI : Les principes fondamentaux du code propre
Dans le monde du développement logiciel, on parle souvent de Clean Code comme d’un idéal : un code clair, maintenable, évolutif et facile à comprendre. Parmi les principes qui guident cette recherche, trois sont particulièrement puissants et universels : DRY, KISS et YAGNI.
J’ai pu constater leur importance de manière très concrète en travaillant sur plusieurs produits aux tailles et aux qualités très différentes, allant d’applications modestes à des systèmes d’envergure avec une base de code massive et des milliers de fonctionnalités. En tant que développeur puis architecte sur ces projets, j’ai pu comparer les environnements où ces principes étaient rigoureusement appliqués et ceux où ils étaient négligés.
Dans les premiers cas, le code était plus clair, plus cohérent et plus simple à maintenir, ce qui permettait aux équipes de livrer rapidement et sereinement de nouvelles fonctionnalités.
Dans les seconds, le non-respect de ces règles engendrait une dette technique considérable, ralentissait les développements, compliquait la maintenance et provoquait une frustration palpable chez les équipes, qui perdaient un temps précieux à déchiffrer et corriger du code mal conçu. Pour ceux qui se retrouvent dans ce type de situation, je recommande vivement la lecture de Working Effectively with Legacy Code de Michael Feathers, un ouvrage de référence qui explique comment reprendre en main et refactorer efficacement des bases de code existantes.
Ce contraste m’a convaincu que ces principes ne sont pas de simples slogans (ni une lubie de développeurs), mais de véritables leviers de performance et de bien-être pour les équipes de développement. Il en va de même pour d’autres règles de conception comme les principes SOLID, qui complètent parfaitement DRY, KISS et YAGNI et méritent également d’être connus et appliqués.
DRY – Don’t Repeat Yourself (Ne vous répétez pas)
Définition :
Introduit par Andy Hunt et Dave Thomas dans The Pragmatic Programmer, le principe DRY stipule que
Une information, un comportement ou une logique ne doit exister qu’en un seul endroit dans le système.
Pourquoi c’est important :
- Évite la duplication de code → moins de risques d’incohérences.
- Réduit l’effort de maintenance → un seul endroit à modifier.
- Rend le code plus lisible et plus modulaire.
Exemple de violation DRY en Java : dans ce code, on calcul un total dans deux méthodes différentes. Dans la première on le renvoi sans remise, dans la seconde on applique un taux de remise.
public double calculateInvoiceTotal(Invoice invoice) {
if (invoice == null || invoice.getItems() == null) {
return 0.0;
}
return invoice.getItems().stream()
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
}
public double calculateDiscountedTotal(Invoice invoice, double discountRate) {
if (invoice == null || invoice.getItems() == null) {
return 0.0;
}
double total = invoice.getItems().stream()
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
return total * (1 - discountRate);
}
Refactorisation pour éviter la duplication de code : Au lieu de recalculer le total dans la seconde méthode, on se contente d’appeler la première qui envoi le total, avant d’appliquer un taux de remise.
public double calculateInvoiceTotal(Invoice invoice) {
if (invoice == null || invoice.getItems() == null) {
return 0.0;
}
return invoice.getItems().stream()
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
}
public double calculateDiscountedTotal(Invoice invoice, double discountRate) {
return calculateInvoiceTotal(invoice) * (1 - discountRate);
}
Avantages de cette approche :
- Lisibilité accrue : une seule méthode concentre la logique métier, plus facile à comprendre.
- Moins de risque d’erreurs : toute modification de calcul se fait à un seul endroit.
- Maintenance facilitée : pas besoin de parcourir le code pour mettre à jour toutes les occurrences.
- Tests unitaires simplifiés : on peut tester
calculateInvoiceTotal
indépendamment, etcalculateDiscountedTotal
n’a besoin que de tests de comportement. - Évolutivité : si la méthode de calcul change (ex. TVA, remise supplémentaire), l’impact est maîtrisé.
Pour en savoir plus sur ce principe :
- https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
- https://youtu.be/znpdlYgvU3M?si=_fdvWBDr1KGgNE8m
KISS — Keep It Simple, Stupid (Restez simple)
Définition : Le principe KISS stipule que
la solution la plus simple qui fonctionne est souvent la meilleure.
Il met l’accent sur la clarté et la réduction de la complexité accidentelle.
Pourquoi c’est important :
- Réduit la probabilité d’introduire des bugs.
- Facilite la compréhension et la relecture du code.
- Rend le code plus maintenable sur le long terme.
- Accélère l’onboarding des nouveaux développeurs.
Exemple de violation KISS en Java :
ici, on veut vérifier si un chiffre est pair, ou impair en utilisant une opération binaire et en testant le résultat qui est déjà un boolean
public boolean isEven(int n) {
boolean isEvenFlag = (n & 1) == 0;
if (isEvenFlag) {
return true;
} else {
return false;
}
}
On peut simplifier ce code en évitant les opérateurs binaires et sans faire de test sur le résultat :
public boolean isEven(int n) {
return n % 2 == 0;
}
Pour en savoir plus sur ce principe :
YAGNI — You Aren’t Gonna Need It (Vous n’en aurez pas besoin)
Issu de l’Extreme Programming, le principe YAGNI stipule que
Il ne faut pas implémenter aujourd’hui une fonctionnalité que personne ne demande ou dont l’usage n’est pas confirmé.

Pourquoi c’est important :
- Évite de perdre du temps et des ressources sur des développements inutiles.
- Réduit la complexité du code et la dette technique.
- Facilite la maintenance en limitant le périmètre fonctionnel au strict nécessaire.
- Permet de se concentrer sur les besoins réels et prioritaires du produit.
public final class ReportGenerator {
public String generatePdf(Data d) { /* ... */ }
public String generateHtml(Data d) { /* ... */ }
public String generateExcel(Data d) { /* pas demandé */ }
}
Ne pas respecter ce principe peut coûter cher. Voici les principaux coûts à considérer :
- Coût de construction (Cost of Building) : le temps, l’effort et les ressources dépensés pour créer une fonctionnalité (planification, développement, tests). Si elle n’est pas utilisée, c’est un investissement perdu.
- Coût du retard (Cost of Delay) : l’impact économique ou les opportunités manquées liées au retard d’une fonctionnalité plus prioritaire à cause de développements non essentiels.
- Coût de portage (Cost of Carry) : la complexité et la charge supplémentaires qu’une fonctionnalité apporte, rendant le reste du code plus difficile à faire évoluer.
- Coût de réparation (Cost of Repair) : le coût de correction ou d’amélioration ultérieure d’une fonctionnalité inutile ou mal conçue, autrement dit la dette technique.
Pour en savoir plus sur ce principe :
- https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it
- https://www.youtube.com/watch?v=_Al7qI4vMt0
Conclusion
Appliqués ensemble, DRY, KISS et YAGNI offrent un cadre pragmatique :
- un code simple (KISS),
- non dupliqué (DRY),
- focalisé sur le besoin réel (YAGNI).
Formalisez‑les dans vos revues de code et templates de PR pour qu’ils deviennent un réflexe d’équipe.

Adoptez les Conventional Commits pour structurer et automatiser vos projets
Combien de fois avez-vous ouvert l’historique Git d’un projet pour tomber sur des commits du genre :
"fix"
"changement"
"correction bug"
"test"
"wip"
Et vous vous êtes demandé :
- Qu’est-ce que ça corrige exactement ?
- Est-ce que c’est lié à une nouvelle fonctionnalité ou à une refactorisation ?
- Puis-je m’appuyer sur cet historique pour générer un changelog fiable ?
Ce flou est fréquent dans les projets. Et c’est précisément ce que les Conventional Commits viennent résoudre : définir une convention claire pour nommer vos commits, en imposant une structure simple et normalisée.
Qu’est-ce que le format Conventional Commits ?
Les Conventional Commits sont une convention d’écriture de messages de commit Git, destinée à rendre l’historique plus clair, lisible et exploitable par des outils.
Le format standard est :
type(scope): description
[optional body]
[optional footer]
Les types de commits
Le champ <type>
est obligatoire et indique l’intention du changement. Les plus courants sont :
- feat : ajout d’une nouvelle fonctionnalité
- fix : correction d’un bug
- docs : modifications de documentation
- style : changements qui n’affectent pas le sens du code (formatage, indentation…)
- refactor : modifications de code qui n’ajoutent pas de fonctionnalité et ne corrigent pas de bug
- perf : amélioration des performances
- test : ajout ou modification de tests
- build : changements liés au système de build ou aux dépendances
- ci : changements pour la configuration de l’intégration continue
- chore : tâches diverses qui ne modifient pas le code applicatif
Exemple d’un message de commit :
feat(auth): ajout de la fonctionnalité de réinitialisation de mot de passe
Cette fonctionnalité permet aux utilisateurs de réinitialiser leur mot de passe via un lien envoyé par e-mail.
BREAKING CHANGE: Les anciennes URLs de réinitialisation ne sont plus valides.
Le scope
Le scope est optionnel mais recommandé. Il précise la zone du code impactée : un module, un composant, un domaine fonctionnel.
Cela facilite la lecture et permet des changements ciblés dans un changelog.
Le footer
C’est ici qu’on indique des informations de versionning ou de suivi, par exemple :
- BREAKING CHANGE : pour indiquer une incompatibilité avec les versions précédentes
- Liens vers des tickets
- Références d’issues fermées
Pourquoi les adopter ?
Amélioration de la lisibilité et de la collaboration
Les Conventional Commits imposent une discipline dans la rédaction des messages. Cela rend chaque commit plus lisible et compréhensible pour l’ensemble de l’équipe, en particulier lors de la revue de code ou de la recherche de bugs.
Automatisation des tâches
L’un des principaux avantages est la possibilité d’automatiser certaines tâches grâce aux messages standardisés :
- Génération automatique de changelogs : Des plugins Maven comme git-changelog-maven-plugin ou maven-conventional-changelog analysent directement l’historique Git au format Conventional Commits pour générer des changelogs détaillés au format Markdown, HTML ou texte.
- Gestion sémantique des versions : En combinant les Conventional Commits avec un plugin Maven comme git-changelog-maven-plugin (goal
semantic-version
), vous pouvez automatiser le calcul de la prochaine version (major, minor ou patch) en fonction des types de commits détectés (feat
,fix
,BREAKING CHANGE
, etc.).- Un bump de patch (pour un
fix
) - Un bump de minor (pour un
feat
) - Un bump de major (pour un
BREAKING CHANGE
)
- Un bump de patch (pour un
Comment mettre en place les Conventional Commits ?
1. Définir une convention au sein de l’équipe
Avant tout, il est important de former l’équipe à utiliser cette norme et de s’assurer que tout le monde comprend les types et leur utilisation.
2. Utiliser des outils pour valider les commits
Git permet d’exécuter un script avant d’accepter un commit.
Dans un projet Java, on peut utiliser un simple script Bash placé dans .git/hooks/commit-msg
#!/bin/bash
commit_message=$(cat "$1")
pattern="^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([^)]+\))?(!)?: .{1,72}"
if ! [[ $commit_message =~ $pattern ]]; then
echo "Message de commit non valide."
echo "Format attendu : type(scope): description"
echo "Types : build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test"
exit 1
fi
Pour s’assurer que les commits sont conformes avant un déploiement, on peut exécuter ce même script dans le pipeline (ex. Jenkins, GitLab CI).
Cela se fait simplement en important le script dans l’étape de build et en l’appliquant sur la plage de commits à vérifier.
3. Générer automatiquement un changelog
Le plugin Maven git-changelog-maven-plugin permet de générer un changelog à partir de l’historique Git, en tirant parti du format Conventional Commits.
mvn se.bjurr.gitchangelog:git-changelog-maven-plugin:git-changelog
Exemple de sortie :
### Features
* **api:** ajout de la pagination sur /users
* **ui:** ajout du tri par date
### Bug Fixes
* **auth:** corrige la validation des tokens expirés
4. Automatiser le versioning sémantique
Le même plugin propose la goal semantic-version
qui calcule la prochaine version en suivant les règles du Semantic Versioning :
fix
→ incrémente le patchfeat
→ incrémente la minorBREAKING CHANGE
→ incrémente la major
Commande :
mvn se.bjurr.gitchangelog:git-changelog-maven-plugin:semantic-version
Le plugin analyse l’historique Git et renvoie la version calculée.
Cette valeur peut ensuite être injectée dans le pom.xml
ou utilisée pour automatiser une release avec maven-release-plugin
.
Bonnes pratiques
- Relire chaque message de commit avant de valider.
- Ne pas mélanger plusieurs changements sans lien dans un même commit.
- Utiliser le scope pour cibler précisément la zone modifiée.
- Documenter la convention dans un fichier
CONTRIBUTING.md
pour les nouveaux arrivants. - Automatiser au maximum la génération du changelog et le versioning pour éviter les oublis.
Conclusion
Mettre en place les Conventional Commits dans un projet, ce n’est pas qu’une question de style :
- Vous obtenez un historique clair et exploitable
- Vous facilitez l’automatisation (changelog, versioning)
- Vous améliorez la maintenabilité sur le long terme
Avec un simple hook Git pour la validation et le plugin git-changelog-maven-plugin pour la génération et le calcul de version, vous disposez d’un workflow lisible, structuré et prêt pour l’automatisation complète des releases.

Agilité en action : une expérience qui prouve la force du groupe
Cette semaine, j’ai animé une formation sur l’agilité. Mon objectif n’était pas seulement de présenter des concepts ou des pratiques, mais surtout de faire vivre des expériences concrètes pour illustrer deux valeurs essentielles de l’agilité :
La vision commune et la force de l’intelligence collective
Pour rendre ces notions palpables, j’ai proposé l’atelier Lost in the Desert, que j’ai déjà animé plusieurs fois et qui produit à chaque session un résultat marquant : la performance collective surpasse celle des individus, même les plus expérimentés
Pourquoi cet atelier ?
Lorsque l’on parle de vision partagée, on pourrait croire qu’il s’agit simplement de se mettre d’accord. Mais en réalité, c’est bien plus profond : il s’agit de construire ensemble un alignement qui permet de dépasser les biais individuels, les connaissances fragmentées et les intuitions parfois trompeuses.
Cet atelier permet de le démontrer de façon tangible. Peu importe que certaines personnes soient expertes sur le sujet : lorsqu’elles mettent leurs savoirs en commun et discutent des décisions, le résultat collectif est presque toujours supérieur aux résultats individuels.
Cet atelier permets ainsi de :
- Ressentir l’impact d’une vision partagée : construire une cohérence d’équipe autour d’un même objectif.
- Observer la puissance de la collaboration : confronter les points de vue mène à de meilleures décisions.
- Décrypter les dynamiques de groupe : leadership, négociation, écoute… .
Lost in the Desert : le scénario
Imaginez :
Vous êtes en avion, au-dessus d’un désert. Après une panne, l’appareil s’écrase. Vous survivez, avec une poignée d’objets récupérés dans l’épave. Votre mission est de classer ces objets par ordre d’importance pour maximiser vos chances de survie.
Le déroulé de l’atelier
L’atelier dure environ 45 à 60 minutes et se déroule en deux temps :
- Phase individuelle (10mn)
- Chaque participant classe les objets seul, selon ses propres intuitions ou connaissances.
- Phase collective (20mn)
- Les participants se regroupent en équipes et doivent construire ensemble un classement commun, en confrontant leurs arguments et leurs points de vue. Différentes visions stratégiques émergent, se complètent ou s’opposent, jusqu’à ce qu’un consensus soit trouvé. Le classement final est donc le fruit d’une décision collective réfléchie, et non d’une simple addition d’opinions individuelles.
- Scoring
- Comparez chaque classement à celui des experts. Le score correspond à la somme des écarts entre les positions individuelles ou de groupe et la référence.
- Débrief (15–20 min)
- Analyse des interactions, des rôles, des modes de décision, et comparaison des performances.
Il existe un classement “officiel”, établi par des experts de la survie en milieu désertique et l’objectif est de comparer chaque solution (individuelle et collective) à ce classement officiel en calculant un score. Plus le score est bas, plus le classement est proche de la réalité.
Voici la liste des objets proposés :
- Bidon de 4 L d’eau
- Couteau suisse
- Lampe torche avec piles
- Miroir de poche
- Paire de lunettes de soleil (par personne)
- Veste en cuir (par personne)
- Parapluie ou poncho
- Boussole
- Carte de la région
- Parachute
- Kit de compression (pansements)
- Sel ou pastilles de sel
- Pistolet chargé (une seule balle)
- Bouteille de Vodka
- Livre sur la faune désertique
Résultats et enseignements
À chaque fois que j’anime cet atelier, le résultat est le même :
- Le classement collectif est meilleur que les classements individuels.
- Même les participants ayant le plus de connaissances sur le sujet ne surpassent pas l’intelligence collective.
- La discussion permet d’éviter les erreurs d’interprétation, de corriger les biais et d’aboutir à une décision plus pertinente.
C’est exactement ce que l’agilité nous apprend :
Une équipe alignée autour d’une vision commune et qui s’appuie sur le collectif prend toujours de meilleures décisions qu’un individu seul.
Le lien avec l’agilité
Dans un contexte agile, cet atelier illustre parfaitement :
- La nécessité d’une vision partagée : sans alignement, chacun optimise pour lui-même, mais pas pour le groupe.
- La force de la collaboration : mettre en commun des compétences et des perspectives différentes permet de construire une solution plus robuste.
- L’importance des échanges : écouter, challenger, débattre, c’est ce qui fait émerger les meilleures idées.
Conclusion
Lost in the Desert est bien plus qu’un simple jeu de survie : c’est une expérience immersive qui fait ressentir la valeur du collectif.
Quand je vois les participants découvrir que leurs intuitions étaient moins bonnes que les décisions d’équipe, je sais que le message est passé :
Ensemble, on se rapproche toujours des meilleures décisions
Pour en savoir plus sur ce serious game, vous pouvez consulter cet article détaillé sur Lost in the Desert.
Découvrez également cet article sur l’intelligence collective et ses bénéfices.

Dependency Inversion Principle : Le secret d’un code vraiment flexible
Le Dependency Inversion Principle (DIP) est le cinquième et dernier des principes SOLID. Il est souvent perçu comme l’un des plus puissants mais c’est aussi probablement le moins bien compris. Ce principe est fondamental pour créer une architecture flexible, testable et maintenable.
Définition
Les modules de haut niveau ne doivent pas dépendre de modules de bas niveau. Tous deux doivent dépendre d’abstractions.
Les abstractions ne doivent pas dépendre des détails. Ce sont les détails qui doivent dépendre des abstractions.
Autrement dit : ne liez pas vos classes à des implémentations concrètes, mais à des comportements abstraits (généralement des interfaces).
Une analogie visuelle : le robot et l’armoire à bras
Imaginez un robot dans un atelier high-tech. Il possède une interface standard sur son épaule pour connecter un bras. Ce robot peut recevoir n’importe quel bras parmi ceux disponibles dans une armoire : un bras laser, un bras à lame, un bras de précision, un bras avec perceuse…
Le robot ne connaît pas les détails internes de chaque bras. Il sait seulement comment activer ou désactiver un bras via une interface commune. Ce sont les bras qui s’adaptent à cette interface standard, pas le robot qui doit changer sa logique à chaque nouveau type d’outil.
C’est exactement le principe de l’inversion de dépendance : le module de haut niveau (le robot) dépend d’une abstraction (l’interface de connexion), et ce sont les modules de bas niveau (les bras-outils) qui s’y adaptent.
Qu’est-ce qu’une dépendance ?
Une dépendance représente tout objet dont une classe a besoin pour fonctionner. Par exemple, une classe de service peut dépendre d’un autre objet comme un repository, un utilitaire ou encore un service tiers qu’elle va appeler pour accomplir sa tâche.
En Java, on reconnaît une dépendance lorsqu’une classe utilise une autre classe pour fonctionner :
Voici un premier (très mauvais) exemple où la dépendance est directement instanciée à l’intérieur de la classe :
class ServiceA {
private final ServiceB b = new ServiceB();
}
Ici, ServiceA
est étroitement couplé à ServiceB
car elle le crée elle-même. Si l’implémentation de ServiceB
change, ServiceA
devra être modifiée également. Ce couplage fort viole le principe d’inversion de dépendance.
Une amélioration consiste à injecter ServiceB
depuis l’extérieur (on appelle ça l’injection de dépendance) :
class ServiceA {
private final ServiceB b;
public ServiceA(ServiceB b) {
this.b = b;
}
}
Ici, ServiceA
dépend toujours de ServiceB
, même si on ne l’instancie plus à l’intérieur. Ce point est essentiel à bien comprendre : l’injection de dépendance (DI) désigne simplement le fait de passer une dépendance depuis l’extérieur, plutôt que de l’instancier dans la classe. Mais cela ne signifie pas forcément que la dépendance est abstraite. On peut très bien injecter une dépendance concrète, et dans ce cas, le DIP n’est pas respecté.
Le DIP va plus loin : il impose que la dépendance injectée soit une abstraction (généralement une interface), ce qui permet de découpler complètement les modules entre eux. Ainsi, le module de haut niveau reste indépendant des détails d’implémentation, qui peuvent varier, être remplacés ou simulés en test sans modifier le code métier.
Pour appliquer le DIP, il faudrait que ServiceA
ne dépende pas directement de ServiceB
, mais d’une abstraction (une interface ou une classe abstraite) que ServiceB
implémenterait. Ce n’est donc pas la manière dont la dépendance est injectée qui compte, mais de quoi la classe dépend.
interface ServiceBInterface {
void executer();
}
class ServiceBImpl implements ServiceBInterface {
public void executer() {
System.out.println("Service B exécuté");
}
}
class ServiceA {
private final ServiceBInterface b;
public ServiceA(ServiceBInterface b) {
this.b = b;
}
public void faireQuelqueChose() {
b.executer();
}
}
ServiceA
dépend maintenant de l’interfaceServiceBInterface
.- N’importe quelle implémentation (réelle, mock, alternative) peut être injectée.
- On respecte le DIP car le module de haut niveau dépend d’une abstraction. (une interface ou une classe abstraite) que
ServiceB
implémenterait.
Quels sont les problèmes d’une dépendance directe ?
1. Couplage fort
Votre classe est liée à une implémentation spécifique. Si ServiceB
change, ServiceA
devra probablement être modifiée aussi.
2. Difficulté à tester
Il est impossible de remplacer facilement ServiceB
par un mock ou une implémentation de test.
3. Manque de flexibilité
Impossible de substituer dynamiquement un autre comportement sans modifier le code source.
4. Propagation des changements
Un changement dans une classe bas niveau peut se répercuter sur toutes les classes qui en dépendent directement.
Mauvais exemple : dépendance concrète
class NotificationService {
public void envoyer(String message) {
System.out.println("Notification envoyée : " + message);
}
}
class CommandeService {
private final NotificationService notificationService;
public CommandeService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void traiterCommande() {
System.out.println("Commande traitée.");
notificationService.envoyer("Confirmation envoyée.");
}
}
CommandeService
est verrouillé à l’implémentation concrèteNotificationService
.- Impossible de tester
CommandeService
sans réellement envoyer des notifications.
Bonnes pratiques : appliquer le DIP
Étape 1 : créer une abstraction
interface Notificateur {
void envoyer(String message);
}
Étape 2 : faire dépendre le module de haut niveau de cette abstraction
class CommandeService {
private final Notificateur notificateur;
public CommandeService(Notificateur notificateur) {
this.notificateur = notificateur;
}
public void traiterCommande() {
System.out.println("Commande traitée.");
notificateur.envoyer("Confirmation envoyée.");
}
}
Étape 3 : fournir une implémentation concrète au moment de l’exécution
class NotificationEmail implements Notificateur {
public void envoyer(String message) {
System.out.println("Email : " + message);
}
}
class Main {
public static void main(String[] args) {
Notificateur email = new NotificationEmail();
CommandeService service = new CommandeService(email);
service.traiterCommande();
}
}
✅ CommandeService
ne dépend plus de NotificationEmail
, mais de Notificateur
.
➡️ Il est donc facile de remplacer l’implémentation par NotificationSMS
, NotificationSlack
, ou un mock en test.
En résumé
Le principe d’inversion des dépendances vous encourage à :
- dépendre d’abstractions plutôt que d’implémentations concrètes
- inverser le contrôle : ce n’est plus la classe qui instancie ses dépendances, mais l’extérieur qui les injecte
- rendre votre code plus flexible, testable, évolutif
Respecter le DIP, c’est faire un pas vers une architecture propre, modulaire, et durable.
À retenir
- Ne créez pas vos dépendances dans vos classes : injectez-les.
- Définissez des interfaces pour les comportements.
- Faites dépendre vos classes d’abstractions.
Ressources complémentaires

Interface Segregation Principle : Évitez les interfaces surchargées !
Introduction
Le principe de ségrégation des interfaces (Interface Segregation Principle – ISP) est le quatrième des cinq principes SOLID en programmation orientée objet. Ce principe stipule que :
“Les clients ne doivent pas être forcés à dépendre d’interfaces qu’ils n’utilisent pas.”
En d’autres termes, il vaut mieux avoir plusieurs interfaces spécifiques plutôt qu’une interface unique trop large qui impose aux classes implémentant l’interface d’avoir des méthodes inutilisées.
Une façon simple d’expliquer ce principe est d’imaginer une télécommande universelle avec 50 boutons alors que vous n’en utilisez que 5. Si chaque utilisateur devait utiliser cette télécommande géante, ce serait une perte de temps et une source de confusion. En programmation, si une classe doit implémenter des méthodes dont elle n’a pas besoin, cela mène à un code inutilement complexe et difficile à maintenir.
Pourquoi est-ce important ?
Le non-respect du ISP peut entraîner :
- Une dépendance excessive entre les classes, rendant le code moins flexible.
- Exemple : Une interface
OutilMultifonction
impose aux classes implémentant l’interface d’avoir des méthodescouper()
,poncer()
,visser()
etpeindre()
, même si un pinceau n’a besoin que depeindre()
.
- Exemple : Une interface
- Des implémentations inutiles, car les classes doivent souvent fournir des implémentations vides pour des méthodes qu’elles ne devraient pas gérer.
- Exemple : Une interface
Oiseau
avecvoler()
etnager()
. Un manchot peut nager mais ne vole pas, tandis qu’un aigle peut voler mais ne nage pas. Chaque oiseau devrait donc implémenter uniquement ce qu’il sait faire.
- Exemple : Une interface
- Une maintenance plus compliquée, car ajouter une méthode dans une interface large impacte toutes les classes qui l’implémentent.
- Exemple : Ajouter une méthode
scanner()
à une interfaceImprimanteMultifonction
forcera toutes les classes d’imprimantes (même celles sans scanner) à la gérer d’une manière ou d’une autre.
- Exemple : Ajouter une méthode
Mauvais exemple : Une interface trop large
Supposons que nous ayons une interface Oiseau
qui regroupe plusieurs comportements.
interface Oiseau {
void voler();
void nager();
}
class Aigle implements Oiseau {
public void voler() {
System.out.println("L'aigle vole haut dans le ciel");
}
public void nager() {
// L'aigle ne sait pas nager !
throw new UnsupportedOperationException("Un aigle ne nage pas");
}
}
Pourquoi est-ce une violation du ISP ?
Aigle
est forcé d’implémenter une méthodenager()
qu’il ne peut pas utiliser.- Une autre classe
Manchot
devrait aussi implémentervoler()
alors qu’un manchot ne vole pas. - Cela oblige à écrire du code inutile et peut introduire des exceptions inattendues dans le programme.
Bon exemple : Découpage des interfaces
Une meilleure approche consiste à découper cette interface en interfaces spécifiques aux capacités des oiseaux.
interface OiseauVolant {
void voler();
}
interface OiseauNageur {
void nager();
}
class Aigle implements OiseauVolant {
public void voler() {
System.out.println("L'aigle vole haut dans le ciel");
}
}
class Manchot implements OiseauNageur {
public void nager() {
System.out.println("Le manchot nage rapidement");
}
}
Pourquoi est-ce une bonne solution ?
Aigle
n’implémente que l’interfaceOiseauVolant
.Manchot
n’implémente que l’interfaceOiseauNageur
.- Le code est plus clair, plus maintenable et plus évolutif.
Autre mauvais exemple : Une interface mal conçue pour les imprimantes
interface ImprimanteMultifonction {
void imprimer();
void scanner();
void faxer();
}
class ImprimanteBasique implements ImprimanteMultifonction {
public void imprimer() {
System.out.println("Impression en cours");
}
public void scanner() {
throw new UnsupportedOperationException("Cette imprimante ne scanne pas");
}
public void faxer() {
throw new UnsupportedOperationException("Cette imprimante ne faxe pas");
}
}
Problème :
ImprimanteBasique
doit implémenter des méthodes inutiles.- Si on ajoute une méthode
photocopier()
àImprimanteMultifonction
, toutes les classes l’implémentant seront affectées.
Solution correcte : Séparer les interfaces
interface Imprimante {
void imprimer();
}
interface Scanner {
void scanner();
}
interface Fax {
void faxer();
}
class ImprimanteBasique implements Imprimante {
public void imprimer() {
System.out.println("Impression en cours");
}
}
class ImprimanteAvancee implements Imprimante, Scanner, Fax {
public void imprimer() {
System.out.println("Impression en cours");
}
public void scanner() {
System.out.println("Numérisation en cours");
}
public void faxer() {
System.out.println("Envoi du fax en cours");
}
}
Maintenant, chaque classe implémente uniquement ce dont elle a besoin, rendant le code plus clair, maintenable et évolutif.
Conclusion
Le principe de ségrégation des interfaces (ISP) est crucial pour éviter les interfaces surchargées et inutiles. Pour l’appliquer correctement :
- Privilégiez plusieurs interfaces spécifiques plutôt qu’une seule interface générale.
- Évitez d’obliger les classes à implémenter des méthodes inutiles.
- Découpez les interfaces en groupes de responsabilités cohérentes.
En respectant ce principe, vous garantissez un code plus flexible, modulaire et évolutif, rendant la maintenance bien plus simple.
Qu’en pensez-vous ? Avez-vous rencontré des interfaces trop lourdes dans vos projets ? Partagez vos expériences en commentaire !

Liskov Substitution Principle : Gardez vos héritages sous contrôle !
Le principe de substitution de Liskov (Liskov Substitution Principle – LSP) est le troisième des cinq principes SOLID en programmation orientée objet. Formulé par Barbara Liskov en 1987, il stipule que :
“Si S est un sous-type de T, alors les objets de type T doivent pouvoir être remplacés par des objets de type S sans altérer les propriétés du programme.”
En d’autres termes, une sous-classe doit pouvoir être utilisée en lieu et place de sa classe mère sans provoquer de comportement inattendu.
Une façon simple d’expliquer ce principe est d’imaginer des jouets de construction. Si une pièce est censée remplacer une autre mais ne s’adapte pas correctement, la structure entière devient instable. En programmation, c’est la même chose : si une sous-classe ne respecte pas les attentes de la classe mère, le code devient imprévisible et peut causer des erreurs difficiles à corriger.
Ce principe est souvent illustré par l’exemple du carré et du rectangle, une relation d’héritage qui, bien que valide en mathématiques, ne l’est pas nécessairement en programmation orientée objet.
Pourquoi est-ce important ?
Le non-respect du LSP peut entraîner :
- Une violation du polymorphisme, rendant le code moins flexible. Imaginez une classe
Oiseau
avec une méthodevoler()
. Si on crée une sous-classePingouin
qui hérite deOiseau
mais lève une exception dansvoler()
, alors un code qui manipule unOiseau
ne pourra pas toujours voler comme prévu, ce qui casse le polymorphisme. - Des bugs difficiles à identifier, car les remplacements d’instances ne respectent pas les attentes. Une classe
Voiture
dispose d’une méthoderouler()
. Si une sous-classeBateauVoiture
hérite deVoiture
mais modifierouler()
pour naviguer au lieu de rouler, un programme s’attendant à un comportement terrestre pourrait produire des erreurs imprévisibles - Une maintenance plus compliquée, car le code devient plus fragile et imprévisible. Une classe
Animal
avecfaireDuBruit()
est héritée parChien
etPoisson
. SiPoisson
ne peut pas faire de bruit et lève une exception, les développeurs devront sans cesse adapter le code pour gérer des cas particuliers, rendant la maintenance plus complexe.
Mauvais exemple : Violation du LSP avec Rectangle et Carré
Prenons l’exemple populaire du carré et du rectangle, cité plus haut :
class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // Forcer un carré
}
@Override
public void setHeight(int height) {
this.width = height;
this.height = height; // Forcer un carré
}
}
Pourquoi est-ce une violation du LSP ?
Le problème ici est que Square
ne respecte pas les attentes de Rectangle
. Si une méthode manipule un Rectangle
, elle s’attend à pouvoir modifier indépendamment sa largeur et sa hauteur, ce qui n’est pas le cas pour un Square
.
Testons ce comportement :
public static void testRectangle(Rectangle rect) {
rect.setWidth(5);
rect.setHeight(10);
System.out.println("Aire attendue: 50, Aire calculée: " + rect.getArea());
}
public static void main(String[] args) {
Rectangle rect = new Rectangle();
testRectangle(rect);
Rectangle square = new Square();
testRectangle(square); // Problème ici !
}
Sortie attendue :
Aire attendue: 50, Aire calculée: 50
Aire attendue: 50, Aire calculée: 100 <-- Erreur due à la violation du LSP
Ici, le comportement change en fonction de l’implémentation, ce qui brise l’interchangeabilité des classes.
Bon exemple : Respect du LSP avec une conception améliorée
Plutôt que d’avoir une relation d’héritage inappropriée, on peut utiliser la composition et une interface commune.
Refactorisation correcte :
interface Shape {
int getArea();
}
class Rectangle implements Shape {
protected int width;
protected int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square implements Shape {
private int side;
public Square(int side) {
this.side = side;
}
public int getArea() {
return side * side;
}
}
Testons maintenant :
public static void testShape(Shape shape) {
System.out.println("Aire calculée: " + shape.getArea());
}
public static void main(String[] args) {
Shape rect = new Rectangle(5, 10);
testShape(rect);
Shape square = new Square(5);
testShape(square);
}
Sortie correcte :
Aire calculée: 50
Aire calculée: 25
Cette refactorisation respecte le LSP, car les objets Rectangle
et Square
respectent leur propre logique métier tout en partageant une interface commune Shape
.
Autre mauvais exemple : Violation du LSP avec Oiseaux
Un autre exemple courant est l’utilisation incorrecte d’une hiérarchie d’héritage entre les oiseaux :
class Oiseau {
public void voler() {
System.out.println("Cet oiseau vole");
}
}
class Pingouin extends Oiseau {
@Override
public void voler() {
throw new UnsupportedOperationException("Un pingouin ne peut pas voler");
}
}
Pourquoi est-ce une violation du LSP ?
Un code s’attendant à manipuler un Oiseau
supposera que l’oiseau peut voler. Cependant, en remplaçant un Oiseau
par un Pingouin
, nous obtenons une exception non prévue qui casse le comportement attendu.
Solution correcte :
Une meilleure solution consiste à utiliser des interfaces :
interface Oiseau {}
interface OiseauVolant extends Oiseau {
void voler();
}
class Moineau implements OiseauVolant {
public void voler() {
System.out.println("Le moineau vole");
}
}
class Pingouin implements Oiseau {
// Aucun comportement de vol ici
}
Ainsi, les classes ne forcent pas un comportement non applicable et restent substituables sans créer d’erreur.
Conclusion
Le principe de substitution de Liskov est essentiel pour écrire un code robuste et évolutif. Pour l’appliquer correctement :
- Évitez de modifier des comportements attendus dans une sous-classe.
- Ne forcez pas un héritage s’il n’a pas de sens.
- Utilisez des interfaces ou la composition lorsque les comportements diffèrent trop.
En respectant ce principe, vous garantissez un code plus flexible, réutilisable et facile à maintenir.
Ressources complémentaires
Qu’en pensez-vous ? Avez-vous rencontré des violations du LSP dans vos projets ? Partagez vos expériences en commentaire !