Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Inversion de contrôle, Injection de dépendance et Service locator

Poster un commentaire

A l’origine de ces techniques, la volonté de maîtriser la complexité des applications et des frameworks.

Comment s’assurer que le code soit testable unitairement?

Comment maîtriser les impacts d’un correctif ou d’une évolution?

Comment organiser du code en unités logiques plus faciles à comprendre?

Comment ajouter de nouvelles fonctionnalités?

Peut t’on choisir dynamiquement un comportement?

 

Supposons par exemple que nous disposions d’une Dll MyDll, on pourrait écrire typiquement ce type de code:

using MyDll;

class ClasseX
{
   void MaFonction()
   {
       ClasseY maClasseY = new ClasseY();
       maClasseY.LireDonnees();
       maClasseY.TraitementDonnees();
   }
}

C’est la structure du code qui nous intéresse, on pourrait la représenter ainsi: 2015-09-04_09-27-04 On a une classe ClasseX qui constitue notre application. Cette classe utilise ClasseY venue de la Dll. Il existe donc une dépendance de ClasseX envers ClasseY.

Si vous réexaminez la liste (non exhaustive) de questions posées en début de ce texte, il n’est pas difficile de voir les nuages qui se profilent à l’horizon. Par exemple il n’est pas facile de remplacer ClasseY par ClasseZ qui implémente un autre algorithme de traitement des données, surtout si ClasseY est utilisée un peu partout dans l’application. Et ce n’est qu’un exemple, de multiples autres Dll peuvent parfaitement être utilisées tout au long de l’application. La difficulté croît de façon exponentielle.

L’application sera difficilement maintenable.

L’inversion de contrôle

ClasseX a t’elle besoin de savoir qu’elle travaille avec ClasseY ou n’importe qui d’autre? Pas vraiment, ce qui l’intéresse est qu’il existe des méthodes LireDonnees et TraitementDonnees avec une certaine signature. Peu m’importe qui va les implémenter et ce qu’il y a derrière.

Ceci ressemble beaucoup à la définition d’une interface. Donc si au lieu de fournir une classe concrète à ClasseX on lui fournissait une interface similaire à celle-ci:


interface IClasse
{
   void LireDonnees();
   void TraitementDonnees();
}

Le nouveau code ressemblerait à ceci:

using MyDll;
class ClasseX
{
   void MaFonction()
   {
      IClasse maClasse // à voir comment obtenir une instance 
      maClasse.LireDonnees();
      maClasse.TraitementDonnees();
   }
}

Ce qui graphiquement se représente ainsi:

2015-09-04_09-47-56

Vous voyez la grande différence?

ClasseX ne dépend plus d’une implémentation particulière de ClasseY. Il n’est plus de la responsabilité de ClasseX d’instancier ClasseY et de fait les problèmes posés au début deviennent plus clairs, même s’il faudra peut être ajouter d’autres patterns pour les résoudre concrètement.

C’est cette idées que l’on appelle inversion du contrôle (Inversion of Control ou IoC): ClasseX ne crée pas les instances des objets dont elle dépend, elle les reçoit.

Il reste tout de même à résoudre un dernier problème avant de pouvoir écrire du code. Comment fait ClasseX pour obtenir une instance? Vous voyez bien que quelque chose du style:

using MyDll;
class ClasseX
{
   void MaFonction()
   {
      IClasse maClasse = new ClassY(); 
      maClasse.LireDonnees();
      maClasse.TraitementDonnees();
   }
}

Ne fait que déplacer le problème.

L’IoC n’est pas juste un concept, mais également une série de patterns aptes à résoudre cette situation. Dans cet article je vais en proposer deux d’usage très fréquents:

  1. Le service Locator
  2. L’injection de dépendances

Mais ces objets pourraient venir d’autres types de service comme un fichier de configuration, un pattern provider

Pour la suite on va appeler service les dépendances. C’est un terme plus général qui convient mieux à la situation.

 

Notez tout de suite une confusion fréquente. IoC et Injection de dépendances (Dependency Injection ou DI) sont très souvent confondues. C’est une erreur. L’injection de dépendances est simplement un des patterns possibles pour faire de l’IoC, il n’est pas le seul.

Le service Locator

L’idée de base d’un service Locator est de disposer d’un objet qui connaît et sait instancier tous les services dont l’application peut avoir besoin.

Dans notre exemple fil rouge, le service locateur sera une classe qui dispose (entre autres) d’une méthode renvoyant une instance de ClasseY:

class static ServiceLocator
{
   public static IClasse GetClasse() { ... }]
}
class ClasseX
{
   void MaFonction()
   {
      IClasse maClasse = ServiceLocator.GetClasse(); 
      maClasse.LireDonnees();
      maClasse.TraitementDonnees();
   }
}

Graphiquement:

2015-09-04_11-03-37

C’est le pattern de base de certains Framework comme MVVM Light.

Beaucoup de développeurs se sentent gênés avec ce pattern. Le Locator ressemble un peu à une classe fourre-tout, est-ce d’ailleurs facile à tester unitairement?

C’est pourtant un pattern tout à fait valide. Les problèmes rencontrés proviennent en général d’une mauvaise conception du code. Si vous ne devez connaître qu’un gourou en architecture logicielle, ce doit être Martin Fowler. Voici ce qu’il écrit à propos de ce pattern:

I’ve often heard the complaint that these kinds of service locators are a bad thing because they aren’t testable because you can’t substitute implementations for them. Certainly you can design them badly to get into this kind of trouble, but you don’t have to. In this case the service locator instance is just a simple data holder. I can easily create the locator with test implementations of my services.
For a more sophisticated locator I can subclass service locator and pass that subclass into the registry’s class variable. I can change the static methods to call a method on the instance rather than accessing instance variables directly. I can provide thread–specific locators by using thread–specific storage. All of this can be done without changing clients of service Locator.
 Personnellement j’aime bien utiliser un provider pour obtenir une instance du Locator. Cela rend le Locator configurable et apte aux tests unitaires.

L’injection de dépendances

La technique consiste à injecter dans ClasseX l’instance de IClasse dont nous avons besoin. Le passage peut se faire par différents canaux éventuellement cumulables:

  • Le constructeur
  • Les propriétés
  • Une méthode

Par exemple on pourrait avoir ceci:

class ClasseX
{
    void MaFonction(IClasse classe)
    {
        IClasse maClasse = classe); 
        maClasse.LireDonnees();
        maClasse.TraitementDonnees();
    }
}

Ce pattern est le plus souvent utilisé avec des frameworks spécialisés comme Unity. C’est d’ailleurs ainsi qu’il prend toute sa dimension je trouve.

En .Net ils sont assez nombreux, voici d’ailleurs une liste probablement non exhaustive:

http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

 

 

 

 

 

 

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s