Introduction aux principes SOLID en C#
\nLe développement en C# dans l'écosystème .NET nécessite non seulement la connaissance de la syntaxe, mais aussi la capacité de concevoir des systèmes flexibles, maintenables et testables. C'est précisément dans ce but que les principes SOLID ont été créés — cinq règles fondamentales de la programmation orientée objet, formulées par Robert Martin (Oncle Bob).
\nL'acronyme SOLID se décompose comme suit :
\n- \n
- S — Single Responsibility Principle (Principe de responsabilité unique) \n
- O — Open/Closed Principle (Principe ouvert/fermé) \n
- L — Liskov Substitution Principle (Principe de substitution de Liskov) \n
- I — Interface Segregation Principle (Principe de ségrégation des interfaces) \n
- D — Dependency Inversion Principle (Principe d'inversion des dépendances) \n
Dans cet article, nous analyserons chaque principe en pratique avec des exemples en C#, montrerons comment leur violation entraîne des problèmes, et comment une application correcte rend le code plus propre.
\n\n1. Principe de responsabilité unique (SRP)
\nEssence : Une classe ne doit avoir qu'une seule raison de changer. Autrement dit, chaque classe est responsable d'une tâche clairement définie.
\n\nViolation du SRP en C#
\nConsidérons un exemple typique — la classe OrderService, qui en fait trop :
public class OrderService\n{\n public void ProcessOrder(Order order)\n {\n // 1. Sauvegarde en base de données\n SaveToDatabase(order);\n \n // 2. Envoi d'email\n SendEmail(order.CustomerEmail, "Commande confirmée");\n \n // 3. Journalisation\n File.WriteAllText("log.txt", $"Commande {order.Id} traitée");\n }\n \n private void SaveToDatabase(Order order) { /* ... */ }\n private void SendEmail(string email, string message) { /* ... */ }\n}\nProblème : La modification de la logique d'envoi d'email ou de la méthode de journalisation entraînera une modification de la classe OrderService. Cela viole le SRP.
Implémentation correcte du SRP
\nSéparons les responsabilités en services distincts :
\npublic class OrderProcessor\n{\n private readonly IOrderRepository _repository;\n private readonly INotificationService _notificationService;\n private readonly ILogger _logger;\n \n public OrderProcessor(\n IOrderRepository repository,\n INotificationService notificationService,\n ILogger logger)\n {\n _repository = repository;\n _notificationService = notificationService;\n _logger = logger;\n }\n \n public void ProcessOrder(Order order)\n {\n _repository.Save(order);\n _notificationService.SendConfirmation(order.CustomerEmail);\n _logger.Log($"Commande {order.Id} traitée");\n }\n}\n\npublic interface IOrderRepository { void Save(Order order); }\npublic interface INotificationService { void SendConfirmation(string email); }\npublic interface ILogger { void Log(string message); }\nDésormais, chaque classe est responsable uniquement de sa tâche. La modification de la logique de journalisation n'affecte pas le traitement des commandes.
\n\n2. Principe ouvert/fermé (OCP)
\nEssence : Les classes doivent être ouvertes à l'extension, mais fermées à la modification. Autrement dit, vous devez pouvoir ajouter un nouveau comportement sans modifier le code existant.
\n\nViolation de l'OCP en C#
\nExemple classique — un calculateur avec des conditions :
\npublic class AreaCalculator\n{\n public double CalculateArea(object shape)\n {\n if (shape is Rectangle rect)\n return rect.Width * rect.Height;\n else if (shape is Circle circle)\n return Math.PI * circle.Radius * circle.Radius;\n // L'ajout d'une nouvelle forme nécessite la modification de cette méthode !\n throw new NotSupportedException();\n }\n}