Design Pattern d'injection des dépendances en C# .NET

Au début des Design Patterns nous avions vu l'injection des dépendances avec Angular2. Mais ça, c'était au début. Maintenant s'il y a bien un pattern qu'il faut absolument connaitre sur le bout des doigts et utiliser dans ses projets c'est bien l'injection des dépendances car on l'a vu à la fin de l'article précédent, il permet de "Mocker" les objets à des fins de testabilité mais pas seulement.

Le concept d'injection des dépendances s'entre mêle avec celui de l'inversion des contrôles et là, cela se complique. Tout ceci pour maintenir autant que possible un couplage faible entre les composants du logiciel et de pouvoir les interchanger en cas de besoin.

Ce Design Pattern est utiliser au maximum dans le framework ASP.NET C# Core pour permettre à des tierces partie de fournir des éléments essentiels de l'application ainsi on peut choisir d'utiliser autre chose que les briques de bases proposées par Microsoft.

Ici : Thibault Herviou nous dit que : Pour éviter au mieux les dépendances,

il y a trois types d'injections :

  • Injection par mutateur/champs
  • Injection par constructeur
  • Injection par interface

L'injection de dépendance permet de ne pas se soucier de l'instanciation.

Design Pattern d'injection par champ

public class CalculateurService
{
private ParamètresMonteur _paramètresMonteur;

public ParamètresMonteur ParamètresMonteur
{
set { this._paramètresMonteur = value; }
}

public ParamètresUtilisateur ObtenirParamètres( int utilisateurId )
{
_paramètresMonteur.SetUtilisateur( utilisateurId );
return _paramètresMonteur.MonterParamètres();
}
}

Injection par constructeur

public class CalculateurService
{
private ParametresMonteur _parametresMonteur;

public CalculateurService( ParametresMonteur parametresMonteur )
{
this._parametresMonteur = parametresMonteur;
}

public ParametresUtilisateur ObtenirParametres( int utilisateurId )
{
_parametresMonteur.SetUtilisateur( utilisateurId );
return _parametresMonteur.MonterParametres();
}
}

Injection par interface

S'utilise en parallèle de l'injection par constructeur.

public class CalculateurService
{
private IParametresMonteur _parametresMonteur;

public CalculateurService( IParametresMonteur parametresMonteur )
{
this._parametresMonteur = parametresMonteur;
}

public IParametresUtilisateur ObtenirParametres( int utilisateurId )
{
_parametresMonteur.SetUtilisateur( utilisateurId );
return _parametresMonteur.MonterParametres();
}
}

Même, si cette on respecte à la lettre les principe de l'injection de dépendance, il y aura toujours des classes qui devront se charger de l'instanciation des interfaces. Il y a donc encore un faible couplage.

Conteneurs IoC

Une fois maîtrisé le Design Pattern d'injection des dépendances on peut s'attaquer au concept d'inversion des contrôles.

Si l'on a quelques classes faisant appel au service CalculateurService nous auront autant d'instances de IParametresMonteur ce qui rendra l'application difficilement maintenable. Une mauvaise maîtrise du concept et l'on rend la classe ce qui est une très mauvaise idée cela entraîne nombre de problèmes de multi-threading ... Singleton ? mauvaise idée à cause des fuites mémoire. Factory + singleton ? Il reste du couplage faible.

Une seule vraie solution le Design Pattern IoC, l'inversion de contrôle :

public Programe()
{
conteneur = new ConteneurIoc();
var param = new ParamètresMonteur() ;
conteneur.Enregistrer<IParamètresMonteur>( param );
conteneur.Enregistrer<ICalculateurService>(new CalculateurService( param ));
}

Et au moment de vouloir utiliser la classe on fait appel au conteneur :

private void UneMéthode()
{
var service = conteneur.Résoudre<ICalculateurService>();
var paramUtilisateur = service.ObtenirParamètres( 1 );

// la suite
}

Il existe les Frameworks de Conteneurs suivants :

Uniy de Microsoft, Spring.Net, Ninject, Google Guice et bien d’autres encore. .NET Core dispose d'une infra de structure IoC.

L'Adapter le Common Service Locator

Ici Philippe Vialatte nous démontre les différents conteneurs IoC, Spring.NET issu de Java, Unity, Ninject .. Et il introduit le CommonServiceLocator pour permettre de réduire encore le couplage et permettre de laisser l'utilisateur choisir son framework d'IoC ...

Le CommonServiceLocator agit comme un pattern Adpater .NET, petit rappel de l'adapter sur le site de la Dofactory.

Pattern Adpater
Design Pattern Adpater

CommonServiceLocator sur GitHub

Effets pervers de l'utilisation des Design Patterns

Ces techniques ne servent à rien si :

  • on n'aura jamais besoin d'une autre implémentation.
  • on n'aura jamais besoin d'une configuration différente
  • on n'aura jamais besoin d'isoler un ou plusieurs composants (et pas de test unitaires)

C'est bien de le rappeler car les jeunes développeurs fougueux ont une tendance à se jeter sur ce genre de trucs en utilisant tous les Design Patterns possibles qu'ils connaissent et en perdant de vu l'essentiel : à quoi ça sert ?

Have fun!