L’OCP (Open/Closed Principle) est le deuxième principe du SOLID, qui stipule : les entités logicielles doivent être ouvertes à l’extension mais fermées à la modification. Ce principe, formulé par Bertrand Meyer en 1988, permet d’ajouter de nouvelles fonctionnalités sans modifier le code existant. Selon le livre de Robert Martin Clean Architecture (2017), le principe d’ouverture est implémenté via des abstractions et le polymorphisme, minimisant le risque d’erreurs de régression.
Points Clés
OCP (Open/Closed Principle) — le principe d’ouverture à l’extension et de fermeture à la modification. Les classes, modules et fonctions doivent être conçus pour que de nouveaux comportements puissent être ajoutés sans modifier leur code source. L’extension est obtenue par héritage, composition ou substitution d’implémentations d’interfaces.
Bertrand Meyer dans son livre Object-Oriented Software Construction (1988) a décrit l’OCP pour la première fois via l’héritage : la classe de base reste inchangée tandis que les sous-classes étendent son comportement. L’interprétation moderne de l’OCP, proposée par Robert Martin, repose sur le polymorphisme et les interfaces : au lieu de l’héritage, des contrats abstraits sont utilisés.
La différence entre les approches est significative. L’héritage crée un couplage fort entre les classes de base et dérivées. Les interfaces et la composition offrent de la flexibilité : l’implémentation peut être échangée sans modifier le code client. L’OCP moderne concerne l’abstraction, pas l’héritage.
L’OCP polymorphe utilise des classes abstraites ou des interfaces pour définir un contrat. Le code client travaille avec l’abstraction sans connaître l’implémentation concrète. Les nouvelles fonctionnalités sont ajoutées en créant une nouvelle classe qui implémente la même interface — sans la moindre modification du code existant. Cela rend le système résistant au changement et prévisible pour l’extension.
Dans le développement mobile, cette approche est omniprésente : le patron Strategy permet d’échanger des algorithmes (compression d’image, mise en cache, authentification) via une interface unique. Ajouter une nouvelle stratégie ne nécessite pas de modifier le code qui l’utilise.
Implémenter l’OCP commence par isoler le comportement variable dans une abstraction. S’il y a une construction switch ou une chaîne if-else qui vérifie le type d’un objet dans le code — c’est un signal pour appliquer l’OCP. Chaque branche conditionnelle nécessite potentiellement l’ajout d’une nouvelle branche lors de l’extension.
Le processus de refactorisation sous l’OCP comprend trois étapes : identifier l’aspect variable (ce qui peut être étendu), l’isoler dans une interface ou une classe abstraite, réécrire le code client pour travailler avec l’abstraction au lieu de la classe concrète. Ensuite, les nouvelles fonctionnalités sont ajoutées sans modifier le client.
Une précision importante : la fermeture à la modification n’est pas absolue. Si un changement d’exigence affecte l’abstraction elle-même ou le contrat — le changement est inévitable. L’OCP protège contre les changements dans les implémentations, pas dans les contrats. Une bonne conception suppose que les contrats sont stables et les implémentations variables.
Lors de l’évaluation de la compatibilité OCP d’une architecture, il est utile d’examiner les points d’extension. Chaque point où un développeur ajoute if-else ou switch pour un nouveau type est candidat à l’abstraction. Un système conçu selon l’OCP a des points d’extension prévisibles : des interfaces avec une documentation qui dit « implémentez cette interface pour ajouter un nouveau type ». Sous Android, le patron Factory avec ViewModelProvider.Factory en est un exemple clair — ajouter un nouveau type de ViewModel ne nécessite pas de modifier les fabriques existantes.
Les patrons les plus efficaces pour respecter l’OCP dans le développement mobile incluent Strategy, Template Method, Decorator et Factory. Chacun résout le problème d’extension du comportement sans modifier le code existant via différents mécanismes de conception orientée objet.
Strategy permet d’échanger des algorithmes à la volée via une interface commune. Dans le développement iOS, les stratégies sont utilisées pour les animations et la validation de formulaires. Template Method définit le squelette d’un algorithme dans une classe de base, et les sous-classes surchargent les étapes — adapté aux écrans ayant une structure commune mais un contenu différent.
Decorator ajoute dynamiquement du comportement à un objet sans modifier sa classe. Sous Android, Decorator est utilisé pour envelopper un Repository avec une couche de cache ou de journalisation. Factory Method crée des objets via une interface, permettant aux sous-classes de décider quelle classe instancier — la base de la création de dépendances compatible OCP.
Le choix du patron dépend de la stabilité du comportement étendu. Strategy est optimal lorsque les algorithmes sont remplacés entièrement. Template Method — lorsque la structure est fixe mais les étapes varient. Decorator — lorsque l’extension doit être transparente pour le client. Pour la plupart des scénarios sous Android et iOS, Strategy + injection de dépendances est suffisant.
Appliquer ces patrons sans OCP est techniquement possible mais perd son sens. C’est l’OCP qui justifie pourquoi nous introduisons un niveau supplémentaire d’abstraction : pour que le système puisse croître sans réécrire le code existant.
Considérons un exemple Android avec le traitement des paiements. Sans OCP, chaque nouveau système de paiement nécessite des modifications dans la classe gestionnaire. Avec OCP, une nouvelle implémentation d’interface est ajoutée sans modifier le code existant.
// Violation de l’OCP : switch nécessite modification lors de l’ajout d’un nouveau système
class BadPaymentProcessor {
fun process(type: String) {
when (type) {
"card" -> // traitement par carte
"paypal" -> // traitement PayPal
}
}
}
// Conception compatible OCP
interface PaymentMethod {
fun pay(amount: Double)
}
class CardPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
class PayPalPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
// Nouveau système — nouvelle classe, sans modifier le code existant
class ApplePayPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
Un exemple iOS avec validation de champs de texte démontre la même logique via les protocoles Swift :
// Validation compatible OCP
protocol ValidationRule {
func validate(_ input: String) -> Bool
}
struct EmailRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.contains("@")
}
}
struct PhoneRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count == 11
}
}
// Ajouter une nouvelle règle ne nécessite pas de modifier le code du validateur
struct PasswordRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count >= 8
}
}
L’avantage clé de l’OCP dans ces exemples : ajouter ApplePay ou PasswordRule ne nécessite pas de modifier les classes existantes. Le code s’étend horizontalement — via de nouveaux fichiers, pas en modifiant les anciens. Cela réduit le risque de régression et accélère l’implémentation de nouvelles fonctionnalités.
La violation la plus courante est une construction switch ou when basée sur le type d’objet. Chaque fois qu’un nouveau type est ajouté, il faut trouver tous ces switch dans le code et ajouter une nouvelle branche. Un switch oublié est une erreur d’exécution difficile à détecter à la compilation.
Dans le développement mobile, l’OCP est violé lors de l’utilisation de classes enum géantes avec des méthodes qui dépendent de la valeur de l’enum. Ajouter un nouvel élément enum nécessite de modifier chaque switch dans tout le projet. L’alternative est le polymorphisme via une interface, où chaque type implémente son propre comportement.
Une autre violation typique est le God Adapter : RecyclerView.Adapter (Android) ou UITableViewDataSource (iOS) qui gère différents types de cellules via if-else. Chaque nouveau type de cellule nécessite d’étendre l’adaptateur. La solution est un ViewHolder polymorphe avec une méthode bind commune, où chaque type de cellule est responsable de son propre affichage.
Mesures préventives incluent : éviter le switch basé sur le type au profit du polymorphisme, injecter les dépendances via des interfaces et utiliser le patron Factory pour créer des objets selon la configuration. Analyser le code pour les « commutateurs par type » est une partie obligatoire de la revue de code dans les équipes orientées OCP.
Refactoriser une violation existante de l’OCP se fait via Replace Conditional with Polymorphism : chaque branche conditionnelle devient une classe séparée implémentant une interface commune. Le code client est réécrit pour travailler avec l’interface, et l’implémentation concrète est fournie via une fabrique ou un conteneur DI.
Il est important de comprendre que l’OCP et le polymorphisme ne résolvent pas tous les problèmes d’extension. Si l’architecture est mal choisie, l’ajout de nouvelles fonctionnalités nécessitera de modifier non seulement les implémentations mais aussi les contrats. Une bonne architecture prédit les directions d’extension et place les abstractions exactement à ces points. Les investissements dans l’OCP sont d’autant plus rentables que le projet vit longtemps et que les exigences des modules spécifiques changent fréquemment.
Questions Fréquentes
Non. L’OCP interdit de modifier le code existant lors de l’ajout de nouvelles fonctionnalités liées à la même abstraction. Modifier un contrat, corriger des bugs et refactoriser ne sont pas des violations de l’OCP — le principe protège contre les changements en cascade lors de l’extension.
Strategy est une implémentation directe de l’OCP. L’interface de stratégie définit le contrat, le client dépend de l’abstraction, et les stratégies concrètes implémentent le comportement variable. Ajouter une nouvelle stratégie ne nécessite pas de modifier le client — c’est l’ouverture à l’extension avec fermeture à la modification.
Oui, via l’héritage et Template Method : la classe de base définit le squelette de l’algorithme et les sous-classes surchargent les étapes. Cependant, l’héritage crée un couplage fort et est moins flexible que les interfaces. Dans le développement moderne, les interfaces et la composition sont considérées comme la méthode préférée pour implémenter l’OCP.
Le code compatible OCP simplifie les tests : chaque implémentation d’interface est testée isolément. Le code client est testé avec une implémentation mock, permettant de vérifier la logique sans se lier à un comportement spécifique. L’extension du système ne nécessite pas de réécrire les tests existants.
Non. L’OCP est justifié lorsque l’extension fonctionnelle est prévisible. Pour un code stable qui n’est pas prévu d’être étendu, une abstraction supplémentaire est excessive. YAGNI (You Ain’t Gonna Need It) est un bon contrepoids à l’OCP : l’abstraction est introduite lorsqu’une deuxième variante de comportement apparaît, pas de manière préventive.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi