Principes SOLID en C#

Parce que c'est souvent demandé en entretien, c'est utile d'avoir un rappel rapide.

SOLID regroupe 5 principes qui aident à concevoir un code orienté objet maintenable : responsabilité unique, ouverture/fermeture, substitution de Liskov, ségrégation d'interface, inversion des dépendances.

Principes SOLID

S — Single Responsibility (Responsabilité unique)

Une classe doit n'avoir qu'une seule raison de changer. Séparer les responsabilités réduit le couplage.

// SRP: OrderService handles order logic, ILogger handles logging
public interface ILogger
{
  void Log(string message);
}

public class FileLogger : ILogger
{
  public void Log(string message)
  {
    // ...
  }
}

public class OrderService
{
  private readonly ILogger _logger;
  public OrderService(ILogger logger)
  {
    _logger = logger;
  }

  public void PlaceOrder(Order order)
  {
    // business logic
    _logger.Log("Order placed");
  }
}
O — Open/Closed (Ouvert/Fermé)

Les entités doivent être ouvertes à l'extension mais fermées à la modification. Utiliser l'héritage ou la composition pour étendre le comportement.

// OCP: extend behavior via inheritance or new implementations
public interface IDiscount
{
  decimal Apply(decimal amount);
}

public class NoDiscount : IDiscount
{
  public decimal Apply(decimal amount) => amount;
}

public class SeasonalDiscount : IDiscount
{
  public decimal Apply(decimal amount) => amount * 0.9m;
}
L — Liskov Substitution (Substitution de Liskov)

Les sous-types doivent pouvoir remplacer les types de base sans altérer le comportement attendu.

// LSP: a Square should be usable where a Rectangle is expected
public class Rectangle
{
  public virtual int Width { get; set; }
  public virtual int Height { get; set; }
  public int Area() => Width * Height;
}

public class Square : Rectangle
{
  public override int Width
  {
    set
    {
      base.Width = base.Height = value;
    }
  }

  public override int Height
  {
    set
    {
      base.Width = base.Height = value;
    }
  }
}
I — Interface Segregation (Ségrégation des interfaces)

Préférer plusieurs interfaces spécifiques plutôt qu'une seule interface générale. Les clients ne dépendent que des méthodes qu'ils utilisent.

// ISP: small, focused interfaces
public interface IPrinter
{
  void Print(Document d);
}

public interface IScanner
{
  void Scan(Document d);
}

public class MultiFunctionPrinter : IPrinter, IScanner
{
  public void Print(Document d)
  {
  }
  public void Scan(Document d)
  {
  }
}
D — Dependency Inversion (Inversion des dépendances)

Dépendre d'abstractions (interfaces), pas d'implémentations concrètes. Injecter les dépendances de haut niveau via des interfaces.

// DIP: high-level modules depend on abstractions
public interface IRepository<T>
{
  void Add(T item);
}

public class SqlRepository<T> : IRepository<T>
{
  public void Add(T item)
  {
    // persist
  }
}

public class Service
{
  private readonly IRepository<Order> _repo;
  public Service(IRepository<Order> repo)
  {
    _repo = repo;
  }

  public void Save(Order o)
  {
    _repo.Add(o);
  }
}

Résumé

Appliquer SOLID augmente la testabilité et la maintenabilité, mais demande une discipline et peut accroître le nombre de types.

Avantages
  • Meilleure testabilité et séparation des préoccupations
Inconvénients
  • Plus de classes/interfaces à gérer, sur-ingénierie si mal appliqué

Bonnes pratiques

  • Favoriser des interfaces petites et explicites; préférer l'injection de dépendances; éviter les classes trop générales.
SourcesÉcrit le 2025-09-04