Open-Closed Principle

Tout module (package, classe, méthode) doit être ouvert aux extensions
mais fermé aux modifications.
Open-Closed Principle - OCP

Evolution sans modification

Les problèmes de rigidité et de fragilité évoqués précédemment sont liés à une même cause : l'impact des changements sur de nombreuses parties de l'application. Chacun de ces changements oblige à modifier du code existant, ce qui est à la fois coûteux et risqué. Pour éviter cela, le principe d'ouverture/fermeture énoncé par Bertrand Meyer stipule que tout module (package, classe, méthode) doit être à la fois :

  • ouvert aux extensions : le module peut être étendu pour proposer des comportements qui n'étaient pas prévus lors de sa création.
  • fermé aux modifications : les extensions sont introduites sans modifier le code du module.

En d'autres termes, l'ajout de fonctionnalités doit se faire en ajoutant du code et non en éditant du code existant.

L'abstraction comme moyen d'ouverture/fermeture

L'ouverture/fermeture se pratique en faisant reposer le code "fixe" sur une abstraction du code amené à évoluer. En d'autres termes, l'OCP consiste à séparer le commun du variable, en faisant reposer le commun sur une définition stable du variable.

Deux mécanismes principaux permettent de mettre en place l'abstraction préconisée par l'OCP :

  • En programmation objet, l'abstraction repose sur l'utilisation de classes d'interface (classes abstraites en C++, interfaces en Java) ,
  • En programmation générique, l'abstraction repose sur l'utilisation de templates.

Utilisation de la "délégation abstraite"

Dans le schéma suivant, A gère les cas c1 et c2. Si un nouveau cas c3 doit être géré, il faut modifier le code de A en conséquence :

Le code de A peut être ouvert/fermé aux modifications en introduisant une classe d'interface I dont dérivent des classes C1 et C2 correspondant aux cas c1 et c2 :

Puisque A repose uniquement sur l'interface I, il devient possible d'ajouter un nouveau cas c3 sous la forme d'une classe C3 dérivée de I, sans avoir à modifier A :

Ce mécanisme d'abstraction consiste à extraire la partie variable du code que l'on souhaite ouvrir/fermer : on introduit une sorte de "plug" dans le code pour lui permettre d'accueillir les évolutions futures.

Utilisation des templates

Les templates permettent de définir des abstractions de type, comme dans l'exemple suivant ou il est possible d'utiliser la méthode foo avec n'importe quelle nouvelle classe du moment que celle-ci possède une méthode bar :

template <class T>
    void foo(T& t)
    {
    ...
    t.bar();
    ...
    }

Note: Ceci n'est cependant qu'un exemple de mise en oeuvre de l'OCP en programmation générique.

On retrouve ainsi de nombreux exemples d'ouverture/fermeture dans la Standard Template Library du C++, que l'on peut même considérer par nature comme un cas d'ouverture/fermeture :

  • les conteneurs sont ouverts/fermés au traitement de nouveaux types,
  • les algorithmes sont ouverts/fermés au traitement de nouveaux ensembles.

L'OCP se retrouve dans de nombreux Design Patterns

De nombreux Design Patterns peuvent être vus comme des cas de mise en pratique de l'OCP. Par exemple :

  • Strategy : le code initial est ouvert/fermé à l'ajout de nouveaux algorithmes en ayant recours à une classe d'interface commune à tous les algorithmes. Cela permet d'ajouter de nouveaux algorithmes sans changer le code client.
  • Abstract factory : le code client est ouvert/fermé à la création de nouveaux objets.
  • Template method : on ferme la structure générale d'une méthode, en ouvrant certaines sous-parties à de nouvelles implémentations. Cela permet de changer les sous-parties sans modifier la structure de base.
  • Visitor : la structure parcourue est ouverte/fermée à l'ajout de nouveaux algorithmes de traitement.

L'application de l'OCP est un choix stratégique

L'OCP est un principe incontournable lorsque l'on parle de flexibilité du code. Par contre, une erreur classique consisterait à vouloir ouvrir/fermer toutes les classes de l'application en vue d'éventuels changements. Cela constitue une erreur dans la mesure où la mise en oeuvre de l'OCP impose une certaine complexité qui devient néfaste si la flexibilité recherchée n'est pas réellement exploitée.

Il convient donc d'identifier correctement les points d'ouverture/fermeture de l'application, en s'inspirant :

  • des besoins d'évolutivité exprimés par le client,
  • des besoins de flexibilité pressentis par les développeurs,
  • des changements répétés constatés au cours du développement.

La mise en oeuvre de ce principe reste donc une affaire de bon sens, sachant que la meilleure heuristique reste la suivante : on n'applique l'OCP que lorsque cela simplifie le design.

L'OCP s'applique également dans une démarche itérative

En préconisant d'anticiper le changement, ce principe semble s'opposer à certains principes d'Extreme Programming ("You ain't gonna need it" et "Do the simplest thing that could possibly work"). La contradiction n'est cependant qu'apparente :

  • L'OCP a pour but de réduire le coût du changement dans le logiciel : il facilite donc en cela l'approche itérative/incrémentale.
  • L'ouverture/fermeture du code n'est pas obligatoirement faite dans un design "up-front" : elle peut (doit) au contraire n'être mise en place qu'au cours de l'activité permanente de refactoring, lorsque les cas concrets d'extension se présentent.

_____
Article original : http://www.objectmentor.com/publications/ocp.pdf
Voir également OpenClosedPrinciple et OpenClosedPrincipleAndXp sur Wiki.