OCP: Open-Closed Principle
Ce post fait partie d’une serie se proposant de présenter les principes SOLID. Vous trouverez l’introducton ainsi que les liens vers les autres articles
ici
.
Définition
“les entités logicielles telles que les classes et les méthodes doivent être ouvertes pour l'extension, mais fermées à la modification.” (Bertrand Meyer)
C’est-à-dire que leur comportement peut être étendu ou bien complètement modifié si elles doivent remplir de nouveaux besoins (ouverte à l’extension) tout en s’assurant que leur code source ne peut pas être directement modifié, aucune modification n’est autorisée (fermé à la modification).
Comment l’appliquer
Après avoir lu cette définition, vous devez vous dire que cela semble contradictoire ! Comment une classe ou une méthode peut être à la fois ouverte pour extension et fermée à la modification ?!
Cela signifie simplement qu’une application doit être structurée de manière à pouvoir ajouter des fonctionnalités en touchant au minimum au code existant. A chaque modification de code, la possibilité de rajouter des bugs existe. Des impacts imprévus peuvent survenir amenant à la modification du comportement de la classe et donc de l’application. L’implémentation devient fragile, difficilement maintenable et sujette aux régressions.
Le principe OCP est également à l’origine de plusieurs bonnes pratiques de développement Objet comme par exemple:
- Les membres (field) d’une classe doivent toutes être privés et non pas public ou encore protected: Lorsque les membres d’une classe changent, chaque fonction qui dépend de ces membres doivent être modifiées. Donc, aucune fonction qui dépend d'un membre public ou protected ne peut être fermé par rapport à ce membre.
- Pas de variables global: même cas que précédement, chaques méthode qui va dépendre d’une variable global ne sera pas fermé par rapport à cette variable.
- Pas d’opération sur les types: cela entraine tres souvent l’impossibilité de fermer une méthode…on le verra dans l’exemple suivant et dans ce cas, il existe certains patterns qui nous assurent un respect du principe
Privilégier ces patterns, c’est éviter les violations du principe OCP!
Exemple!
Passons a un exemple afin de mieux cerner le problème: prenons le cas d’une classe qui écris des messages de Log en base de donnée ou sur un fichier.

1: public class Logger
2: {
3:
4: private TypeLog _typelog;
5:
6: public Logger(TypeLog typelog)
7: {
8: _typelog=typelog;
9: }
10:
11: public void WriteLog(string message)
12: {
13: switch (_typelog)
14: {
15: case TypeLog.File:
16: WriteInFile(message);
17: break;
18: case TypeLog.DataBase:
19: WriteInDataBase(message);
20: break;
21: default:
22: break;
23: }
24: }
25:
26: private void WriteInFile(string message)
27: {//Do stuff
28: }
29:
30: private void WriteInDataBase(string message)
31: {//Do stuff
32: }
33:
34: }
35:
36: public enum TypeLog
37: {
38: File,
39: DataBase
40: }
41:
42:
Le problème étant que si je dois en plus prendre en compte l’écriture via un service distant…on est obligé d’ajouter une valeur à l’énum et de modifier la classe Logger (ajout de la méthode + prise en compte). Bref on viole OCP deux fois.
La solution passe par l’implémentation du pattern Stratey (et un peu d’IoC au passage) comme il suit:

On ne dépend plus que de l’interface ILogger et on injecte dans le constructeur l’objet qui est en charge d’effectuer ce travail. Si on souhaite ajouter une nouvelle stratégie d’écriture de log, on a juste a implémenter l’interface ILogger et ca roule 
La classe logger devient:
1: public class Logger
2: {
3:
4: public ILogger _logger;
5:
6: public Logger(ILogger logger)
7: {
8: _logger= logger;
9: }
10:
11: public void WriteLog(string message)
12: {
13: _logger.WriteLog(message);
14: }
15:
16: }
On a l’interface ILogger:
1: public interface ILogger
2: {
3: void WriteLog(string message);
4: }
ET les loggers:
1: public class FileLogger : ILogger
2: {
3:
4: public void WriteLog(string message)
5: {
6: //do stuff
7: }
8: }
9:
10: public class ServiceLogger : ILogger
11: {
12:
13: public void WriteLog(string message)
14: {
15: //do stuff
16: }
17: }
18:
19:
20: public class DataBaseLogger : ILogger
21: {
22:
23: public void WriteLog(string message)
24: {
25: //do stuff
26: }
27: }
Dans cette exemple, on n’aborde pas la manière de choisir le “bon” logger, on pourrais imaginer dans ce cas une “Abstract Factory” qui aurait donc la charge de cette responsabilité.
Vous trouverez les sources des exemples ici:http://solidincsharp.codeplex.com/
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :