Aller au contenu

Responsabilité unique (SRP)

Principe de responsabilité unique (SRP)

Introduction

Une classe, un service ou un composant ne doit avoir qu'une seule responsabilité fonctionnelle. Ainsi, il ne doit exister qu'une seule raison susceptible de le faire évoluer. Prenons l'exemple une classe ayant une méthode permettant de réaliser les tâches suivantes :

  1. Calculer le montant total de la facture

  2. Enregistrer la facture dans une base de données

  3. Envoyer la facture par mail

public class InvoiceService
{
    public void CreateInvoice(Invoice invoice)
    {
        // 1 - Calcul du montant total de la facture
        invoice.Total = invoice.Lines.Sum(line => line.Quantity * line.UnitPrice);

        // 2 - Sauvegarde en base de données
        _repository.Save(invoice);

        // 3 - Envoi par email au client
        var smtpClient = new SmtpClient("smtp.algowin.fr");
        smtpClient.Send("contact@algowin.fr", invoice.CustomerEmail, 
            "Facture Juillet 2026", 
            $"Votre facture du mois de Juillet 2026.");
    }
}

Dans cette méthode, nous pouvons observer qu'il existe plusieurs raisons de la modifier :

  • Lorsque la règle de calcul du montant total est modifiée

  • Lorsque le "support" sur lequel la facture enregistrée est modifié

  • Lorsque le contenu de l'e-mail est modifié

En appliquant le principe de responsabilité unique, nous pourrions écrire les classes et interfaces suivantes :

public interface IInvoiceCalculator
{
    decimal CalculateTotal(Invoice invoice);
}

public class InvoiceCalculator : IInvoiceCalculator
{
    public decimal CalculateTotal(Invoice invoice)
         => invoice.Lines.Sum(line => line.Quantity * line.UnitPrice);
}

public interface IInvoiceRepository
{
    void Save(Invoice invoice);
}

public class InvoiceRepository : IInvoiceRepository
{
    public void Save(Invoice invoice)
    {
         // Sauvegarde de la facture
         _repository.Save(invoice);
    }
}

public interface IInvoiceCommunicator
{
    void SendInvoiceCreatedEmail(Invoice invoice);
}

public class InvoiceCommunicator : IInvoiceCommunicator
{
    public void SendInvoiceCreatedEmail(Invoice invoice)
    {
        var smtpClient = new SmtpClient("smtp.company.com");
        smtpClient.Send("contact@company.com", invoice.CustomerEmail,
            "Votre facture",
            $"Votre facture d'un montant de {invoice.Total} € a été créée.");
    }
}

public class InvoiceService
{
    private readonly IInvoiceCommunicator _calculator;
    private readonly IInvoiceRepository _repository;
    private readonly IInvoiceCommunicator _emailCommunicator;

    public InvoiceService(
        IInvoiceCalculator calculator,
        IInvoiceRepository repository,
        IInvoiceCommunicator emailCommunicator)
    {
        _calculator = calculator;
        _repository = repository;
        _emailCommunicator = emailCommunicator;
    }

    public void ProcessInvoice(Invoice invoice)
    {
        invoice.Total = _calculator.CalculateTotal(invoice);
        _repository.Save(invoice);
        _emailCommunicator.SendInvoiceCreatedEmail(invoice);
    }
}

Chaque interface et classe associée possède désormais une responsabilité clairement identifiée :

  • L'interface IInvoiceCalculator réalise les calculs liés aux factures

  • L'interface IInvoiceRepository gère leur persistance dans la base de données

  • L'interface IInvoiceCommunicator prend en charge les communications avec le client

  • L'interface IInvoiceService orchestre ces différents traitements

Cette séparation des responsabilités rend le code plus lisible, facilite les tests unitaires. Par exemple, l'interface IInvoiceCalculator peut être testée indépendamment de la persistance en base de données ou de l'envoi d'e-mails. De même, l'interface IInvoiceService peut être testée en simulant (mockant) les dépendances qu'elle orchestre. Aussi, elle limite l'impact des évolutions, puisqu'une modification dans une classe donnée n'impacte pas les autres classes.

Ainsi, la responsabilité des classes est respectée et le code sera plus compréhensible.