Introduction à l'injection de dépendance en csharp

Lorsque deux classes entrent en interaction, un couplage se crée. Il existe cependant une technique permettant de limiter l’impact de ce couplage sur la maintenabilité du code. Il s’agit de l’injection de dépendance.

L’injection de dépendance permet d’établir de façon dynamique une relation entre les classes. Elle permet ainsi de découpler les éléments les uns des autres et de ne plus décrire explicitement leurs relations dans le code. De ce fait, elle facilite la modification ultérieure du code et l’ajout de nouvelles fonctionnalités.

Cas pratique

Prenons l’exemple d’une voiture. Celle-ci a besoin d’un volant pour tourner :

public class                        Car
{
    private SteeringWheel           steeringWheel;

    public                          Car()
    {
        this.steeringWheel = new SteeringWheel();
    }

    public void                     TurnLeft()
    {
        this.steeringWheel.Rotate("left");
    }
}

var steeringWheel = new SteeringWheel();
var car = new Car(steeringWheel);
car.TurnLeft();

La classe Car crée une instance de SteeringWheel sur laquelle elle va appeler la méthode Rotate pour tourner.

Mettons maintenant que nous souhaitions utiliser un volant qui permet de klaxoner, représenté par la classe SteeringWheelWithKlaxon. Nous devons donc modifier Car pour utiliser cette nouvelle classe ce qui présente des risques d’erreurs et de régression.

Pour palier ce problème, nous pouvons utiliser un contrat :

public interface                    ISteeringWheel
{
    void                            Rotate(string direction);
}

La classe Car ne reçoit plus l’objet spécifique steeringWheel mais un objet respectant le contrat ISteeringWheel (qui implémente les contraintes d’un volant).

public class                        Car
{
    private ISteeringWheel          steeringWheel;

    public                          Car(ISteeringWheel steeringWheel)
    {
        this.steeringWheel = steeringWheel;
    }

    public void                     TurnLeft()
    {
        this.steeringWheel.Rotate("left");
    }
}

Dorénavant, quand notre voiture aura besoin d’un volant qui contient un klaxon, il suffira de ne plus lui donner l’objet steeringWheel mais l’objet steeringWheelwithklaxon. Le comportement du volant reste inchangé pour la voiture. Quant au klaxon, il ne sait pas que le bouton le déclenchant est aussi un volant. Dans le découplage des éléments, les objets ne doivent pas savoir avec qui ils interagissent, quelles sont leurs caractéristiques spécifiques respectives, ni même qui les appelle.

steeringWheelWithKlaxon peut donc respecter les contrats ISteeringWheel (précédemment défini) ainsi que IKlaxon:

public interface                    IKlaxon
{
    void                            PressKlaxon(int second);
}

public class                        SteeringWheelWhithKlaxon : ISteeringWheel, IKlaxon
{
    public void                     Rotate(string direction)
    {
        // ...
    }

    public void                     PressKlaxon(int second)
    {
        // ...
    }
}

Lors de la réalisation de SteeringWheelWithKlaxonbutton, la personne en charge du développement ne devra plus modifier la classe Car car elle n’utilise plus directement l’objet steeringWheel mais l’interface ISteeringWheel.

Penchons-nous maintenant sur leur utilisation :

// Simple car.
ISteeringWheel steeringWheel = new SteeringWheel();
Car car1 = new Car(steeringWheel);

car1.TurnLeft();

// Simple car + klaxonButton.
ISteeringWheel steeringWheelWithButton = new SteeringWheelWhithKlaxon();
IKlaxon klaxonButton = steeringWheelWithButton as IKlaxon;
Car car2 = new Car(steeringWheelWithButton);

car2.TurnLeft();
klaxonButton.PressKlaxon(5);

À la lecture de ce code, quel est l’intérêt du découplage de car, steeringWheel et steeringWheelWithKlaxon alors qu’ils sont appelés directement dans le code ?

C’est ici que l’injecteur de dépendance entre en jeu (pour la suite de cet article nous utiliserons Unity comme container d’injection).

var container = new UnityContainer().LoadConfiguration();

var steeringWheel = container.Resolve<ISteeringWheel>();
Car car = new Car(steeringWheel);

car.TurnLeft();

Nous ne créons plus l’objet steeringWheel mais demandons au container de nous donner l’objet qui respecte le contrat ISteeringWheel. Le lien entre la classe SteeringWheel et l’interface ISteeringWheel est réalisé dans le fichier de configuration de l’application app.config comme suit :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
  </configSections>

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <alias alias="ISteeringWheel" type="Ioc.Interfaces.ISteeringWheel, Ioc" />
    <namespace name="Ioc" />
    <assembly name="Ioc" />

    <container>
      <register type="ISteeringWheel" mapTo="SteeringWheel" />
    </container>

  </unity>
</configuration>

Le container d’injection va dynamiquement, au chargement de l’application, faire l’association entre les éléments indiqués dans la configuration.

<register type="ISteeringWheel" mapTo="SteeringWheel" />

A présent, l’association entre les éléments étant dynamique, nous allons pouvoir “injecter” un élément dans un autre. Pour cela, nous allons associer via le container notre voiture à notre volant !

Commençons par notre voiture qui doit, pour pouvoir être dynamiquement appelée, être associée à un contrat.

public interface                    ICar
{
    void                            TurnLeft();
}

public class                        Car : ICar
{
    private ISteeringWheel                  steeringWheel;

    public                          Car(ISteeringWheel steeringWheel)
    {
        this.steeringWheel = steeringWheel;
    }

    public void                     TurnLeft()
    {
        this.steeringWheel.Rotate("left");
    }
}

Puis ajoutons le lien entre Car et ICar dans la configuration de notre container :

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  <alias alias="ISteeringWheel" type="Ioc.Interfaces.ISteeringWheel, Ioc" />
  <alias alias="ICar" type="Ioc.Interfaces.ICar, Ioc" />

  <namespace name="Ioc" />
  <assembly name="Ioc" />

  <container>
    <register type="ISteeringWheel" mapTo="SteeringWheel" />
    <register type="ICar" mapTo="Car">
    </register>
  </container>
</unity>

A ce stade, notre solution ne fonctionnera pas car le constructeur de la classe Car attend un objet respectant le contrat ISteeringWheel.

Il ne nous reste plus qu’à “injecter” dynamiquement notre objet respectant le contrat ISteeringWheel dans le constructeur de la classe Car :

<register type="ICar" mapTo="Car">
  <constructor>
    <param name="steeringWheel"/>
  </constructor>
</register>

A présent que les associations sont toutes faites et que notre volant est “injecté” dans le constructeur de notre voiture, nous pouvons laisser notre container nous donner tout ce qu’il faut sans avoir à lui spécifier quelque élément que ce soit. Notre code appelant se transforme alors en :

var container = new UnityContainer().LoadConfiguration();
var car = container.Resolve<ICar>();

car.TurnLeft();

Plus besoin de préciser que nous avons besoin d’un volant pour notre voiture, le container le sait et le fait pour nous !

Demain, lorsque nous voudrons remplacer notre volant par celui avec un klaxon, il suffira d’implémenter la classe SteeringWheelWithKlaxon et de modifier la configuration de notre container pour que ISteeringWheel ne soit plus lié à SteeringWheel mais à SteeringWheelWithKlaxon. Il n’y aura donc aucune modification de code.

En conclusion

Nous venons de voir que le container permet non seulement de découpler les éléments mais aussi de les associer dynamiquement, ce qui facilite et réduit grandement les risques d’erreur dans les appels.

Au prochain article sur l’injection de dépendance nous verrons comment encapsuler dynamiquement une méthode dans une autre pour non seulement découpler les éléments mais, en plus, découpler les couches de notre application.

Publié le 27 octobre 2014

Notre vision des choses vous correspond ? Vous avez envie de travailler avec nous ?