SOLID

De Banane Atomic
Révision datée du 3 avril 2022 à 19:53 par Nicolas (discussion | contributions)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigationAller à la recherche

Liens

Définition

Acronyme définissant les 5 premiers principes de la programmation orientée objet.

Single Responsibility Principle Une classe ne doit avoir qu'une seule responsabilité.
Open Close Principle Une classe doit pouvoir être étendue sans devoir être modifiée.
Liskov Principle Les sous classes doivent respecter le contrat de leur classe parente.
Interface Segregation Principle Découper les interfaces pour que les classes qui les implémentent utilisent toutes les méthodes des interfaces.
Dependency Inversion Principle

Single Responsibility Principle

Une classe ne doit avoir qu'une seule responsabilité

Csharp.svg
// la classe Contact est responsable de la manipulation des contacts
class Contact
{
    public Contact()
    {
        // elle ne doit pas se charger de la manière dont les messages de log sont écris
        File.AppendAllText(@"C:\Error.log", "message d'erreur");
        // mais plutôt faire appel à un gestionnaire de log
        log.Error("message d'erreur");
  • lister les méthodes et essayer de les grouper par noms ou actions. Si plusieurs groupes sont identifiés, c'est un indicateur que la classe peut être redécoupée.
  • lister les dépendances externes (appel direct à la bdd, API, fichier de log). Ces dépendances peuvent être externalisées.

Open Closed Principle

Une classe, une méthode doit pouvoir être étendue (Open for extension) sans pour cela devoir être modifiée (closed for modification).

Csharp.svg
class Contact
{
    public Contact(Countries country)
    {
        // si l'on veut étendre le code pour gérer de nouveaux pays on va devoir modifier le code de Contact
        if (country == Countries.France) {
            PhoneCountryCode = "33";
        } else if (country == Countries.Switzerland) {
            PhoneCountryCode = "41";
        }

// Solution 1 : injecter le PhoneCountryCode
    public Contact(Countries country, string phoneCountryCode)
    {
        PhoneCountryCode = phoneCountryCode;

// Solution 2 : utiliser une factory
    public Contact(Countries country)
    {
        PhoneCountryCode = PhoneCountryCodeFactory.GetPhoneCountryCode(country);

// Solution 3 : polymorphisme
class FrenchContact : Contact
{
    public FrenchContact()
    {
        PhoneCountryCode = "33";

Les 3 solutions permettent d'étendre le code pour gérer de nouveaux pays sans avoir à modifier la classe Contact.

Liskov Substitution Principle

Les sous-classes doivent respecter le contrat de leur classe parente.
Les sous-classes doivent être substituables par leur classe de base sans que cela modifie le comportement du programme.

Csharp.svg
abstract class Contact
{
    public abstract string PhoneCountryCode { get; }
}

class FrenchContact : Contact
{
    public override string PhoneCountryCode => "33";
}

class NoCountryContact : Contact
{
    public override string PhoneCountryCode => throw new InvalidOperationException();
}

var contactList = new List<Contact>();
cl.Add(new FrenchContact());
cl.Add(new NoCountryContact());

foreach (var contact in contactList)
{
    // NoCountryContact.PhoneCountryCode va générer une InvalidOperationException
    Console.WriteLine(contact.PhoneCountryCode);
}

Interface Segregation Principle

Les classes ne doivent pas implémenter d'interfaces dont elles n'utilisent pas toutes les méthodes.
Découper les interfaces pour qu'elles correspondent au besoin.

Csharp.svg
interface IDatabaseAccess
{
    void Load();
    void Save();
}

class Contact : IDatabaseAccess
{
    public void Load() { /* */ }
    public void Save() { /* */ }
}

class ReadOnlyContact : IDatabaseAccess
{
    public void Load() { /* */ }
    // Ici on ne veut pas implémenter la méthode Save car le contact est en lecture seule
    public void Save() { /* */ }
}

// Solution: on découpe l'interface IDatabaseAccess
interface IDatabaseAccessLoad
{
    void Load();
}

interface IDatabaseAccess : IDatabaseAccessLoad
{
    void Save();
}

class Contact : IDatabaseAccess
{
    public void Load() { /* */ }
    public void Save() { /* */ }
}

class ReadOnlyContact : IDatabaseAccessLoad
{
    public void Load() { /* */ }
}

Dependency Inversion Principle

Le code business ne doit pas dépendre du code de bas niveau (accès à la bdd, log, appels web) mais utiliser des d'abstractions (injection d'interfaces).

Csharp.svg
class FileLog
{
    public static void Log(string message)
    {
        File.AppendAllText(@"C:\Error.log", message);
    }
}

class Contact
{
    // Contact dépend de FileLog
    private FileLog _log = new FileLog();

    public Contact()
    {
        _log.Log("message");

// Solution: utiliser une interface et injecter la dépendance
class FileLog : ILog

class Contact
{
    private ILog _log;

    public Contact(ILog log)
    {
        _log = log;
        _log.Log("message");