Chargement...

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


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