Le découplage de composants

Il est commun en développement d’utiliser des interfaces pour découpler les composants.

Prenons cet exemple. Nous avons créer un composant appelé MyEmailSender qui permet d’envoyer des emails. Nous voulons donc créer une interface qui va définir toutes les fonctions publiques requises pour envoyer un email : IEmailSender.

Tout autre composant de notre application qui nécessite un envoi de mail, par exemple une notification de mise à jour UpdateNotifier, peut envoyer un message en se référant uniquement aux méthodes de l’interface. Il n’y a pas de dépendance ente UpdateNotifier et MyEmailSender.

Interface

En utilisant IEmailSender nous nous assurons qu’il n’y a pas de dépendance direct entre UpdateNotifier et MyEmailSender. Nous pourrions remplacer MyEmailSender par un autre provider Mail ou utiliser une implémentation sous forme d’un mock pour les tests.

Problème

Il reste souvent un problème. Comme je l’ai noté dans un précédent article c’est la manière d’appeler cette interface. On se retrouve le plus souvent avec ce genre d’implémentation :

public class UpdateNotifier{
    public void SendUpdate()
    {
        IEmailSender mySender = new MyEmailSender();
        /* Appels des fonctions
           de l'interface pour
           configurer le mail
        */
        mySender.SendEmail();
    }
}

Nous sommes donc seulement à une partie du chemin du découplage de composants. La classe UpdateNotifier utilise bien les éléments de l’interface pour appeler les méthodes mais il nécessite de créer une instance de MyEmailSender pour travailler.

En faisant cela UpdateNotifier dépend à la fois de IEmailSender et de MyEmailSender :

Couplage fort

Solution

Nous avons donc besoin d’obtenir des objets qui implémentent une interface sans avoir besoin de créer l’objet directement. Cette solution s’appelle l’Injection de Dépendance (Dependency Injection) connu aussi sous le nom Inversion de Contrôle (Inversion Of Control).

Injection de dépendance

L’injection de dépendance est un design pattern qui complète le découplage que l’on a commencé en ajoutant l’interface IEmailSender.

L’injection de dépendance se fait en deux parties. La première est de retirer toutes les dépendances vers les classes concrètes du composant. Nous allons faire cela en passant au constructeur de la classe les implémentations des interfaces.

public class UpdateNotifier
{
    private IEmailSender mySender;
    public UpdateNotifier(IEmailSender emailSender)
    {
        mySender = emailSender;
    }
    
    public void SendUpdate()
    {
        /* configuration de l'email */
        mySender.SendEmail();
    }
}

Nous avons cassé la dépendance entre UpdateNotifier et MyEmailSender. UpdateNotifier demande un objet qui implémente IEmailNotifier dans son constructeur mais il ne sait pas ou ne se préoccupe pas de l’objet et n’est plus responsable de sa création.

Conclusion

Les dépendances sont injectés dans UpdateNotifier à l’exécution. Une instance d’un objet implémentant IEmailSender sera créé et passé au constructeur durant son instanciation. Il n’y a plus de dépendance à la compilation.

Comme les dépendances sont gérés à l’exécution, nous pouvons choisir quelle implémentation sera utilisée quand nous exécuterons l’application. Nous pouvons choisir entre différents providers email ou injecter une implémentation Mock pour les tests.