Principes SOLID en C#
Application des principes SOLID en C# avec des exemples : pour un code flexible, maintenable et testable.
En développement logiciel, les principes SOLID, définis par Robert C. Martin, sont cinq règles fondamentales visant à créer des systèmes plus flexibles, lisibles et maintenables dans la programmation orientée objet (POO). En C#, ces principes sont largement utilisés comme lignes directrices dans la conception des classes.
1. Single Responsibility Principle (SRP) – Principe de responsabilité unique
Une classe ne doit avoir qu’une seule responsabilité. En d’autres termes, elle ne doit avoir qu’une seule raison de changer. Si une classe gère plusieurs tâches (par ex. enregistrer des données + générer un rapport), sa maintenance devient plus complexe et le risque d’erreurs augmente.
// Incorrect : Sauvegarde des commandes et imprime des factures
public class OrderManager
{
public void SaveOrder(Order order) { /* sauvegarde */ }
public void PrintInvoice(Order order) { /* impression */ }
}
// Correct : Chaque classe a une seule responsabilité
public class OrderRepository
{
public void Save(Order order) { /* sauvegarde */ }
}
public class InvoicePrinter
{
public void Print(Order order) { /* impression */ }
}
2. Open/Closed Principle (OCP) – Principe Ouvert/Fermé
Les classes doivent être ouvertes à l’extension mais fermées à la modification.
Cela signifie qu’on peut ajouter de nouvelles fonctionnalités sans changer le code existant.
Cela se fait généralement via des abstract class ou des interface.
// Incorrect : Ajouter un nouveau mode de paiement nécessite de modifier la classe
public class PaymentService
{
public void Pay(string type)
{
if (type == "CreditCard") { /* ... */ }
else if (type == "PayPal") { /* ... */ }
}
}
// Correct : Il suffit d’ajouter une nouvelle classe
public interface IPayment
{
void Pay();
}
public class CreditCardPayment : IPayment
{
public void Pay() { /* ... */ }
}
public class PayPalPayment : IPayment
{
public void Pay() { /* ... */ }
}
3. Liskov Substitution Principle (LSP) – Principe de substitution de Liskov
Les classes dérivées doivent pouvoir être utilisées à la place de leurs classes de base. Autrement dit, une sous-classe doit fonctionner correctement partout où sa super-classe est attendue. Une sous-classe ne doit pas violer le contrat défini par la super-classe.
public abstract class Bird
{
public abstract void Fly();
}
public class Sparrow : Bird
{
public override void Fly() { Console.WriteLine("Le moineau vole"); }
}
// Incorrect : Les pingouins ne volent pas, mais Fly() est imposé
public class Penguin : Bird
{
public override void Fly() { throw new NotImplementedException(); }
}
Ici, la classe Penguin viole le LSP.
Car la classe abstraite Bird impose l’hypothèse « tous les oiseaux volent ».
Il vaut mieux définir des interfaces distinctes comme IFlyingBird et INonFlyingBird.
4. Interface Segregation Principle (ISP) – Principe de ségrégation des interfaces
Une classe ne doit pas être obligée d’implémenter de grandes interfaces contenant des méthodes dont elle n’a pas besoin. Il faut préférer des interfaces plus petites et spécifiques.
// Incorrect : Tous les animaux doivent marcher, voler et nager
public interface IAnimal
{
void Walk();
void Fly();
void Swim();
}
// Correct : Découpage en petites interfaces
public interface IWalkable { void Walk(); }
public interface IFlyable { void Fly(); }
public interface ISwimmable { void Swim(); }
public class Dog : IWalkable, ISwimmable
{
public void Walk() { /* ... */ }
public void Swim() { /* ... */ }
}
5. Dependency Inversion Principle (DIP) – Principe d’inversion des dépendances
Les classes de haut niveau ne doivent pas dépendre directement des classes de bas niveau. Les deux doivent dépendre d’abstractions (interfaces ou classes abstraites). Ce principe est à la base de l’injection de dépendances.
// Incorrect : Service dépend directement de ConsoleLogger
public class Service
{
private readonly ConsoleLogger _logger = new ConsoleLogger();
}
// Correct : Dépendance envers une abstraction
public interface ILogger
{
void Log(string msg);
}
public class ConsoleLogger : ILogger
{
public void Log(string msg) => Console.WriteLine(msg);
}
public class Service
{
private readonly ILogger _logger;
public Service(ILogger logger) { _logger = logger; }
}
Avantages
- Améliore la lisibilité et la maintenabilité du code.
- Fournit une architecture plus résistante aux changements.
- Facilite les tests unitaires.
- Réduit la duplication de code et améliore la réutilisabilité.
TL;DR
- SRP : Chaque classe doit avoir une seule responsabilité.
- OCP : Ouvert à l’extension, fermé à la modification.
- LSP : Les sous-classes doivent pouvoir remplacer les super-classes.
- ISP : Préférer de petites interfaces ciblées aux grandes.
- DIP : Dépendre des abstractions et non des classes concrètes.
Articles connexes
Classes, Objets, Propriétés et Méthodes en C#
Découvrez comment les classes, objets, propriétés et méthodes en C# constituent les fondements de la programmation orientée objet.
Encapsulation, Héritage et Polymorphisme en C#
Apprenez l’encapsulation, l’héritage et le polymorphisme en C# avec des exemples pour maîtriser les bases de la POO.
Gestion des exceptions en C# (try, catch, finally)
Apprenez à gérer les exceptions en C# avec les blocs try, catch et finally afin de traiter les erreurs de manière sûre avec exemples.
Interfaces et Classes Abstraites en C#
Découvrez les interfaces et classes abstraites en C#, leurs différences et quand les utiliser pour concevoir un code maintenable.
Méthodes d’extension en C#
Apprenez les méthodes d’extension en C# pour ajouter des fonctionnalités aux types existants sans modifier leur code.
Principes de l’Injection de Dépendances en C#
Apprenez les principes de l’Injection de Dépendances en C#, la gestion des dépendances et le couplage faible avec exemples.