Principe OCP en Software Craftsmanship, Guide Complet et Exemples Java
Introduction
Modifier un morceau de code et casser au passage une fonctionnalité qui marchait : la scène se répète dans toutes les équipes que j'audite. Chaque changement déclenche une cascade de régressions imprévues. La cause profonde est presque toujours la même : le code n'a pas été conçu pour être étendu sans être modifié.
Le principe OCP (Open/Closed Principle) attaque ce problème de front. Sur des dizaines de missions, j'ai vu le même pattern : à chaque nouvelle règle métier, on retouche du code existant, on casse des tests, la vélocité s'effondre. OCP coupe ce cycle. Le principe, formulé par Bertrand Meyer dans Object-Oriented Software Construction (1988) puis repris par Robert C. Martin parmi les SOLID, pose qu'une fois écrit, un module doit être ouvert aux extensions mais fermé aux modifications.
Cet article explique ce que recouvre vraiment OCP, son rôle en Software Craftsmanship, et comment l'appliquer dans des projets Java. Exemples de code à l'appui, avec les pièges classiques à éviter.
Qu’est-ce que le Principe OCP (Open/Closed Principle) ?
Le principe OCP fait partie des cinq principes SOLID, qui sont des règles fondamentales pour concevoir du code propre, maintenable et évolutif. OCP signifie "Ouvert/Fermé", en référence à l'idée que les entités (classes, modules, fonctions) doivent être ouvertes à l’extension mais fermées à la modification.
En d’autres termes, une fois qu’une classe ou un module est en production, vous ne devriez pas avoir à le modifier pour ajouter de nouvelles fonctionnalités. Cela peut sembler contradictoire au début : comment peut-on ajouter de nouvelles fonctionnalités sans toucher au code existant ? La réponse réside dans la conception du code dès le départ. Au lieu de modifier le comportement d'une classe, vous devez la rendre extensible via des interfaces, des abstractions ou des classes dérivées.
Anticipez les axes d'extension qui ont déjà été identifiés par le métier (nouveaux types, nouvelles règles connues). Inutile d'abstraire à l'aveugle, c'est du YAGNI. OCP s'applique là où le changement est probable, pas partout.
Prenons un exemple concret pour illustrer cela.
Exemple 1 : Cas classique sans OCP
Imaginons que vous développez une application qui calcule les salaires d'employés. Vous avez une classe Employee avec une méthode calculateSalary() qui calcule le salaire en fonction du type d'employé.
class Employee {
private String type;
private double baseSalary;
public Employee(String type, double baseSalary) {
this.type = type;
this.baseSalary = baseSalary;
}
public double calculateSalary() {
if (type.equals("Manager")) {
return baseSalary * 2;
} else if (type.equals("Developer")) {
return baseSalary * 1.5;
} else {
return baseSalary;
}
}
}
À première vue, ce code semble fonctionnel. Mais que se passe-t-il si vous devez ajouter un nouveau type d'employé, par exemple, un "Designer" avec un salaire différent ? Vous devrez modifier la méthode calculateSalary() et ajouter une nouvelle condition. Plus le nombre de types d'employés augmente, plus la méthode devient complexe et sujette aux erreurs.
** Plus votre code est rempli de conditions (comme les if et else ici), plus il devient fragile. Chaque modification future peut casser une partie existante.
Ce design viole le principe OCP car à chaque nouvelle fonctionnalité (nouveau type d'employé), vous devez modifier la classe existante. Cela rend le code plus difficile à maintenir et à tester.
Exemple 2 : Application du principe OCP avec des interfaces
Voyons maintenant comment nous pourrions refactorer ce code pour respecter le principe OCP. La clé ici est de rendre le comportement de calcul extensible, sans avoir à modifier la classe existante. Nous pouvons utiliser des interfaces ou des classes abstraites pour séparer le calcul des salaires en fonction des types d’employés.
interface SalaryCalculator {
double calculateSalary(double baseSalary);
}
class ManagerSalaryCalculator implements SalaryCalculator {
@Override
public double calculateSalary(double baseSalary) {
return baseSalary * 2;
}
}
class DeveloperSalaryCalculator implements SalaryCalculator {
@Override
public double calculateSalary(double baseSalary) {
return baseSalary * 1.5;
}
}
class Employee {
private double baseSalary;
private SalaryCalculator salaryCalculator;
public Employee(double baseSalary, SalaryCalculator salaryCalculator) {
this.baseSalary = baseSalary;
this.salaryCalculator = salaryCalculator;
}
public double calculateSalary() {
return salaryCalculator.calculateSalary(baseSalary);
}
}
Avec ce design, si vous avez besoin d’ajouter un nouveau type d’employé, par exemple un "Designer", vous n’avez plus à toucher à la classe Employee. Vous créez simplement une nouvelle classe qui implémente SalaryCalculator :
class DesignerSalaryCalculator implements SalaryCalculator {
@Override
public double calculateSalary(double baseSalary) {
return baseSalary * 1.8;
}
}
L'ajout d'un nouveau type d'employé devient alors une extension naturelle, sans modification du code existant. C'est exactement ce que vise OCP : un code flexible et modulaire qui évite les modifications sources de bugs.
** Quand vous concevez votre code avec OCP, réfléchissez toujours en termes d'extension future. Posez-vous la question : "Si je devais ajouter une nouvelle fonctionnalité, est-ce que je serais obligé de modifier le code existant ?"
Vous voulez concevoir vos classes pour les étendre sans jamais les rouvrir ?
Choisir le bon axe d'abstraction, savoir quand une interface vaut mieux qu'un if, sentir quand l'extension dérape en sur-ingénierie : ça ne s'apprend pas dans un article, ça se travaille. En mentoring 1:1, je relis votre code avec vous et on retravaille vos points d'extension ensemble, jusqu'à ce que le réflexe devienne le vôtre.
Pourquoi OCP est essentiel dans le Software Craftsmanship
Le principe OCP ne se limite pas seulement à écrire du code plus propre. Il est au cœur de ce qu'on appelle le Software Craftsmanship, un mouvement qui prône l’écriture de code non seulement fonctionnel, mais aussi élégant et maintenable. Respecter le principe OCP vous aide à :
- Réduire les risques de régressions : ne pas toucher au code existant signifie ne pas casser ce qui est déjà testé. Sur mes missions en grandes DSI, ce point se traduit directement par moins d'incidents de production et des releases plus fréquentes.
- Faciliter les extensions : ajouter une nouvelle fonctionnalité devient une opération chirurgicale, sans avoir à plonger dans des classes complexes pour y greffer une modification.
- Améliorer la collaboration en équipe : un code modulaire et bien structuré se laisse étendre par d'autres développeurs sans qu'ils craignent de casser des fonctionnalités voisines.
** Un code qui ne suit pas OCP est souvent source de conflits lors du travail en équipe. Si plusieurs développeurs doivent modifier la même classe, des erreurs sont inévitables.
Dans le contexte du Software Craftsmanship, suivre OCP revient à prendre soin de son code sur le long terme. Vous étendez plutôt que de patcher, et vous arrêtez de courir après les bugs nés des modifications du code existant.
Comment appliquer OCP dans vos projets Java
Au-delà des interfaces déjà vues, deux autres mécanismes Java permettent d'appliquer OCP en pratique : les classes abstraites et le pattern Strategy.
Exemple 3 : Utilisation de classes abstraites pour respecter OCP
Une autre manière courante de respecter OCP consiste à passer par des classes abstraites. Elles définissent un comportement commun partagé entre plusieurs sous-classes, tout en laissant à ces dernières la liberté de redéfinir certains points.
Prenons l'exemple précédent de calcul des salaires. Voici comment vous pourriez utiliser des classes abstraites pour atteindre un résultat similaire tout en respectant OCP :
abstract class Employee {
protected double baseSalary;
public Employee(double baseSalary) {
this.baseSalary = baseSalary;
}
// Méthode abstraite que chaque sous-classe implémentera
public abstract double calculateSalary();
}
class Manager extends Employee {
public Manager(double baseSalary) {
super(baseSalary);
}
@Override
public double calculateSalary() {
return baseSalary * 2;
}
}
class Developer extends Employee {
public Developer(double baseSalary) {
super(baseSalary);
}
@Override
public double calculateSalary() {
return baseSalary * 1.5;
}
}
Avec cette approche, la classe Employee pose un cadre général pour tous les types d'employés ; chaque sous-classe prend en charge l'implémentation spécifique du calcul des salaires. Étendre le système revient à ajouter une nouvelle sous-classe : le code existant reste intact.
** Les classes abstraites sont très utiles pour éviter la duplication de code commun tout en respectant OCP.
Les pièges courants et comment les éviter
OCP a l'air simple sur le papier. L'appliquer sans déraper l'est moins. Trois erreurs reviennent constamment :
- Complexité inutile : OCP doit rendre le code extensible, pas complexe. Empiler des couches d'abstraction ou des hiérarchies d'héritage juste pour cocher la case OCP alourdit le code sans bénéfice. Appliquez le principe là où il a un sens, pas partout.
- Abus d'héritage : créer de longues chaînes d'héritage au nom d'OCP est un classique. L'héritage rend pourtant le code rigide. Dans la plupart des cas, la composition (utiliser des objets plutôt que des sous-classes) donne un résultat plus flexible.
Entre héritage et composition, préférez la composition. Vous obtenez des modules plus flexibles et plus réutilisables.
- Modifier le code existant alors qu'on pourrait l'éviter : la tentation d'aller patcher une classe pour répondre à un nouveau besoin est forte. Si vous le faites souvent, le design initial manque de flexibilité, c'est lui qu'il faut revoir.
Conclusion : Rendre votre code plus flexible avec OCP
OCP est un levier puissant pour concevoir un code extensible, robuste et maintenable. Limiter les modifications du code existant réduit le risque d'erreur et fluidifie l'ajout de fonctionnalités.
Interfaces, classes abstraites ou composition : peu importe l'outil, OCP donne à vos applications la marge d'adaptation aux changements futurs. En Software Craftsmanship, respecter ce principe signale un engagement clair : écrire du code qui fonctionne et qui évolue avec élégance.
OCP n'est qu'une pratique parmi les 100 que j'applique pour coder propre
Cet article détaille une seule pratique : concevoir pour l'extension plutôt que pour la modification. Le Craft Bundle réunit les 100 pratiques craft que j'applique au quotidien pour écrire du code maintenable, du choix de l'abstraction au découpage des responsabilités. Ce sont celles que l'IA ne vous apprendra jamais, parce qu'elle ne les a jamais vues tenir en production sur le long terme.
FAQ : Réponses aux questions fréquentes
1. Comment savoir si j’applique correctement le principe OCP ?
Si vous pouvez ajouter de nouvelles fonctionnalités à votre code sans toucher au code existant, vous êtes probablement sur la bonne voie. Utilisez des abstractions comme les interfaces et les classes abstraites pour séparer les comportements.
2. Est-ce que suivre OCP rend le code plus compliqué ?
Cela dépend de la manière dont vous l’implémentez. Si vous ajoutez trop de couches d’abstraction, cela peut effectivement compliquer les choses. L’objectif est de trouver un équilibre où votre code reste compréhensible tout en étant extensible.
3. Que faire si j’ai déjà du code qui ne respecte pas OCP ?
Vous pouvez refactorer progressivement votre code en identifiant les parties les plus susceptibles de changer, puis en les isolant à l’aide d’abstractions. Refactoriser petit à petit permet d’améliorer le code sans introduire trop de risques.
4. OCP est-il toujours applicable ?
Non, parfois, il n’est pas nécessaire d’appliquer OCP, surtout si vous travaillez sur une petite partie du code qui ne changera probablement jamais. Le principe doit être appliqué là où il est pertinent, c’est-à-dire là où il y a un potentiel de changement ou d’extension.
Ressource gratuite : 10 signaux que votre équipe tech est en danger
10 signaux d'alarme pour identifier les problèmes systémiques cachés dans votre équipe avant qu'ils deviennent critiques. Auto-diagnostic inclus : 5 minutes pour savoir où vous en êtes.


