Les principes SOLID appliqués en C# (5/5)
Publié le 06/09/2022
Par  Christophe MOMMER

Avant toute chose, il faut savoir que chacun de ces principes peut également être découvert en vidéo. La vidéo pour ce principe se trouve ici : https://youtu.be/22Unq2sS7bs

Dernier des principes SOLID, le principe D stipule qu'il faut dépendre d'abstractions et non d'implémentations.

OK, la définition, c'est bien mignon, mais ça veut dire quoi en fait ?

D pour Dependency Inversion principle

Prenons un exemple simple : vous codez une application pour une banque. Cette application permet de convertir des euros en dollars américains et vice-versa. Les fondamentaux de ce type de conversion repose sur un taux de change, qui varie en fonction du marché. Ce taux du marché peut être récupéré par le biais d'une API. Dans votre routine de conversion, il faut donc récupérer le taux de change pour convertir les euros vers les dollars (ou inversement).

C'est cool, mais donc ?

Prenons l'exemple de ce bout de code :

public async Task<decimal> Conversion(decimal montantEuro)
{
   var client = new HttpClient();
   var tauxMessage = await client.GetStringAsync("http://api.debanque.fr/taux-de-change?from=EUR&to=USD");
   var taux = decimal.Parse(tauxMessage);
   var montantDollar = montantEuro * taux;
   return montantDollar;
}

A noter : ici, l'API n'existe pas, c'est juste pour illustrer l'exemple.

Que fait ce code ? Il récupère le taux de change sur une API avec un client HTTP, puis converti ce montant afin de faire le calcul. 

Ok, imaginons maintenant les problèmes suivants : l'API n'est plus disponible, pas d'accès réseau donc il faut utiliser une valeur en cache, l'URL de l'API change, on décide d'utiliser un taux stocké en base de données localement, etc. Bref, autant de scénario qui impose de retoucher le code pour gérer ces nouveaux comportements ou ces imprévus.

C'est quoi le rapport avec le principe en fait ?

Comme on l'a dit au début, D veut dire que notre code business doit dépendre d'abstractions. Ici, dans cet exemple, c'est qu'au lieu de dépendre d'une implémentation concrète basée sur un appel HTTP, il faut abstraire la récupération du taux de change. Par exemple :

public enum Currency {
   Euro,
   DollarAmericain
}

public interface ITauxDeChangeManager
{
    Task<decimal> GetTaux(Currency from, Currency to);
}

Super, une interface ! Et donc ?

Maintenant, notre code métier/notre service doit dépendre de cette interface pour faire son traitement :

private ITauxDeChangeManager _tauxDeChange;

public MonService(ITauxDeChangeManager tauxDeChange)
{
    _tauxDeChange = tauxDeChange
}

public async Task<decimal> Conversion(decimal montantEuro)
{
   var taux = await _tauxDeChange.GetTaux(Currency.Euro, Currency.DollarAmericain);
   var montantDollar = montantEuro * taux;
   return montantDollar;
}

Je pense que je commence à comprendre...

Déjà, le code est de suite plus clean : on "délocalise" la responsabilité de récupérer le taux de change dans un autre objet (on respecte ainsi le premier principe). Mais au-delà de ça, on utilise une abstraction. En quoi est-ce important ? Tout simplement car avec un système d'injection de dépendance digne de ce nom, il sera possible de substituer les implémentations pour cette abstraction sans plus avoir à toucher l'implémentation de cette méthode (on respecte aussi le second principe, c'est dingue !).

En bref : du fait que notre code métier utilise une interface, on peut lui donner n'importe quelle classe qui implémente cette interface. Cerise sur le gâteau, les interfaces peuvent facilement s'implémenter en tant que Mock/Fake/Stub dans un projet de tests automisés !

Note : on peut aussi utiliser une classe abstraite si on veut