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 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.

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

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ée l'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'instance 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 l'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
}

Framework de Conteneurs :

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 la 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
Pattern Adpater

Effets pervers

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 truc en perdant de vu l'essentiel : à quoi ça sert ?

Have fun!