Interface | Injection de dépendance

Oui on peut s’imaginer une grosse seringue en lisant cet intitulé, mais non ce n’est pas ce que vous croyez même si c’est « bien trippant » 🙂

Origine SOLID

L’injection de dépendance trouve son origine dans dans un des principes SOLID qui sont représentes certaines « bonnes manières de faire » basés sur une longue expérience de programmation, d’observation et de réflexion. Ces principes sont l’oeuvres de Michael Feathers et Robert C. Martin alias Uncle Bob et s’applique pour la programmation orientée objet. L’application de ces principes pendant le développement d’un logiciel devrait favoriser la fiabilité et une robustesse comme qualité en bout de ligne. Ce qui n’est pas peu dire. L’inversion de dépendance étant le D de solid, c’est celui-là que nous verrons un peu plus en détail.

Inversion de dépendance

Ce principe s’applique pour la programmation orientée objet et sert à découpler les classes dépendantes entre elles. En inversant la dépendance, les modules en viennent à dépendre d’abstraction plutôt que de classes concrètes ou d’objet. Ça tombe bien, on peut se servir d’interface pour créer une abstraction 🙂 Voyons comment cela se concrétise.

En pratique

D’abord, l’exemple ici utilisé fait suite à l’article précédent concernant la fabrique.

Alors, dans l’article précédent, on utilisait une interface qui jouait un certain rôle et nous demandions à la fabrique de nous fabriquer un objet qui pouvait jouer le rôle pour l’accoler à l’interface. L’injection de dépendance en vient à peu près au même principe, mais nous reléguons la tâche de demander quel objet doit être utilisé pour une interface à un autre module. La classe, ou dépendance, est alors commandé et servi ailleurs et injectée dans notre objet qui doit alors utiliser cette dépendance.

Injection de dépendance par le constructeur

C’est un forme assez commune et pratique pour injecter une dépendance dans un autre classe en utilisant le constructeur de la classe.

Alors, j’ai créé une nouvelle classe ConsumerDependencyInjection qui va remplacer la classe ConsumerAvecFactory que j’ai utilisée dans l’article précédent.

Je retire le code qui fait appel à une fabrique et je place un paramètre de type IRestClient dans le constructeur qui sera passé par la suite à une variable membre restClient du même type.

Ensuite, on utilise la variable en respectant le rôle établi par l’interface IRestClient.

Voici la classe ConsumerDependencyInjection :

public class ConsumerDependencyInjection
    {
        private const string request = "http://127.0.0.1/queryThat?option=this";

        private IRestClient restClient;
        private ParameterSet _parameterSet;

        public ConsumerDependencyInjection(ParameterSet parameterSet, IRestClient restClient)
        {
            _parameterSet = parameterSet;
            InitializeComponent(restClient);
        }

        public void SendRequest()
        {
            var restRequest = new RestRequest()
            {
                Request = request
            };
            restClient.AddRestRequest(restRequest);
        }

        public HttpStatusCode HttpStatusCode { get; set; } = HttpStatusCode.BadRequest;

        public string RestClientName { get; set; }

        private void InitializeComponent(IRestClient restClient)
        {

            restClient.RestServerResponded += (sender, restEvent) =>
            {
                HttpStatusCode = restEvent.HttpStatusCode;
                RestClientName = restEvent.RestClientName;
            };
        }
}

Maintenant, pour injecter la dépendance, on peut créer l’objet qui implémente l’interface IRestClient et instancier la classe ConsumerDepencencInjection en injectant l’objet IRestClient. Dans mon exemple, j’injecte un objet de la classe RestClientFake parce que cela va nous amener vers mon prochain article qui mettra en scène des tests unitaire et des interface. Alors voici l’exemple :

public class InterfacePlay
    {
        public InterfacePlay()
        {
            InjectDependency();
        }

        private void InjectDependency()
        {
            var parameterSet = new ParameterSet();
            var restClient = new RestClientFake();
            var consumerDepInjected = new ConsumerDependencyInjection(parameterSet, restClient);
        }
}

En conclusion

L’injection de dépendance tire son origine du D des principes de programmation orientée objet SOLID. Ce principe demande d’inverser les dépendance afin que les objets dépendent plutôt d’abstractions.

En pratique, on peut dépendre d’une interface, qui est une abstraction, et injecter la dépendance dans la classe qui l’utilise par le constructeur.

Cela tend à découpler les modules et apporte une plus grande souplesse au niveau du design et la programmation.

Ce principe aide beaucoup pour tester des modules et c’est ce que nous verrons dans le prochain article.

Interface | La fabrique

Mon dernier article vous présentait les rudiments de base des interfaces et comment il pouvait être implémentés par des exemples en C#. Nous allons reprendre la découverte des interface à partir de ce moment pour aller plus loin en intégrant un patron de conception à notre apprentissage et en l’utilisant pour ajouter de la valeur à nos interface

Patron de conception : La fabrique

Tout d’abord, parlons de la fabrique. Alors ce patron de conception en fait sert à fabriquer des objets qui seront dérivés d’un autre. Généralement on parle de classes concrètes dérivées d’une classe abstraite. La classe qui utilise la fabrique pour se faire « fabriquer » un objet ne connaît pas d’avance quel objet lui sera retourné.

Cette fabrique peut aussi être utilisé pour fabriquer des objets qui ont un lien entre eux, mais pas nécessairement par polymorphisme, comme je l’explique dans le paragraphe précédent. Dans le cas qui nous intéresse ici, le lien qui les unis c’est l’interface que ces classes implémentent.

Intérêt avec notre interface

Comme nous avons vu dans notre exemple précédent, le fait de déclarer l’interface et d’y attacher un objet par la suite dans la même classe ne donne pas de valeur ajoutée. En fait, ça alourdis le code pour aucun bénéfice dans ce cas précis.

La fabrique, dans notre cas d’utilisation d’une interface, va servir à nous fabriquer un objet qui implémente l’interface à la classe qui utilise l’interface. Cette dernière ne sait pas comment la fabrique produit cet objet; elle sait seulement que cet objet joue le rôle décrit dans l’interface et c’est tout ce qu’elle doit savoir

L’intérêt c’est qu’avec un seul rôle à jouer et déterminé dans une interface, on peut distribuer ce rôle à un nombre de classes que l’on veut selon notre besoins. Par exemple, on peut avoir une classe utilisatrice SQL qui utilise une interface IDBClient et deux classes qui implémentent cette interface. Une utilise le client SQL natif de MS et l’autre ODBC. Donc, notre classe utilisatrice peut se connecter sur un serveur SQL en utilisant une ou l’autre selon le scénario. Et cela, sans changer l’implémentation dans la classe utilisatrice de l’interface IDBClient.

Mise en pratique avec notre exemple précédent

Alors, notre exemple précédent est ici et l’ensemble du code concernant les interface se trouve sur Github. Nous finissions notre exemple en déclarant une variable interface IRestClient et, dans la même ligne, y attribuons un objet RestClient qui implémente l’interface comme dans l’exemple ci-bas :

private IRestClient restClient = new RestClient();

Maintenant nous allons utiliser une fabrique pour créer l’objet.

Créer la fabrique

Généralement, dans ce genre de cas, on utilise une méthode statique dans une classe statique pour créer l’objet. Un paramètre est passé à la méthode pour indiquer un contexte et la fabrique crée l’objet selon ce paramètre. Alors, voici comment pourrait être notre fabrique :

namespace InterfacePlay
{
    public enum RestClientType { Real, Fake };

    public static class RestClientFactory
    {
        public static IRestClient CreateRestClient(RestClientType restClientType)
        {
            IRestClient restClient = new RestClientFake();
            switch (restClientType)
            {
                case RestClientType.Real:
                    restClient = new RestClient();
                    break;
            }
            return restClient;
        }
    }
}

Alors nous avons ici une classe statique RestClientFactory qui contient une méthode statique CreateRestClient qui prend en paramètre une énumération RestClientType et qui retourne une interface IRestClient. En fait, c’est plutôt un objet qui implémente l’interface qui est retourné.

Quand la méthode CreateRestClient est appelée, elle crée un objet selon la valeur du paramètre RestClientType qui lui est passé. C’est soit un RestClient ou un RestClientFake dans ce cas-ci.

Remarquez que, peut importe la classe qui est retournée, la condition pour ce faire doit être que la classe implémente l’interface.

Pourquoi le switch dans cette exemple et non pas un if? Parce que vous pouvez avoir besoin de ça si vos implémentez plus que deux classes et c’est par habitude que je l’ai mis ici dans une fabrique 🙂

Voyons maintenant comment utiliser la fabrique dans notre classe utilisatrice.

Utiliser l’interface

Voici donc la classe qui utilise l’interface IRestClient et voyons comment la fabrique est utilisée pour créer un objet et ainsi aider à attribuer cet objet à la variable IRestClient

using System.Net;

namespace InterfacePlay
{
    public class ConsumerAvecFactory
    {
        private const string request = "http://127.0.0.1/queryThat?option=this";

        private IRestClient restClient;
        private ParameterSet _parameterSet;

        public ConsumerAvecFactory(ParameterSet parameterSet)
        {
            _parameterSet = parameterSet;
            InitializeComponent(parameterSet);
        }

        public void SendRequest()
        {
            var restRequest = new RestRequest()
            {
                Request = request
            };
            restClient.AddRestRequest(restRequest);
        }

        public HttpStatusCode HttpStatusCode { get; set; } = HttpStatusCode.BadRequest;

        public string RestClientName { get; set; }

        private void InitializeComponent(ParameterSet parameterSet)
        {
            var restClientType = parameterSet.RestClientType;
            restClient = RestClientFactory.CreateRestClient(restClientType);

            restClient.RestServerResponded += (sender, restEvent) =>
            {
                HttpStatusCode = restEvent.HttpStatusCode;
                RestClientName = restEvent.RestClientName;
            };
        }
    }

    public class ParameterSet
    {
        public RestClientType RestClientType { get; set; }
    }
}

Alors, on a presque la même classe que dans l’article où l’on présente la base de l’utilisation des interfaces à la différence qu’ici l’objet qui implémente l’interface est créé dans la fabrique et que la classe qui se sert de l’interface IRestClient ne sait pas lequel sera retourné.

La ligne de code importante est la suivante et se trouve dans une méthode InitializeComponent appelée à partir du constructeur de la classe. Le paramètre qui sera passé à la fabrique et qui détermine quel objet sera retourné provient du constructeur. Dans un certain sens, on pourrait dire que la condition a été injectée dans le constructeur :

restClient = RestClientFactory.CreateRestClient(restClientType);

Les avantages

Utiliser une fabrique pour passer des objet qui implémente une interface qu’une classe utilise permet d’extensionner les possibilités d’une classe sans en changer une ligne de code et l’application elle même au final.

Par exemple, vous pouvez avoir une classe qui écrit la pensée du jour de l’utilisateur et qui prend cette valeur à travers une interface IClientInput implémentée par un classe qui lit directement à partir du clavier, une autre un répertoire en synchronisation avec le cloud et une autre un service web. Tout ça avec le même code dans la classe qui utilise une interface plutôt que des classes concrètes.

Pour aller plus loin

Au niveau des tests aussi cela donne vraiment beaucoup d’avantage, mais nous verrons cette partie un peu plus tard ainsi que l’injection de dépendance que vous aimerez vraiment beaucoup vous aussi 🙂

Conclusion

Nous avons vue qu’est-ce que le patron de conception de la fabrique. Ensuite, nous avons expliqué comment il pouvait servir avec les interfaces. Nous avons mis cet apprentissage en pratique et amélioré la classe qui utilise l’interface IRestClient que nous avons vu dans l’article précédent et nous avons vue les avantages d’utiliser la fabrique avec les interfaces.

Le prochain article portera encore sur les interfaces, c’est un sujet passionnant et on commence seulement à en faire le tour.

N’hésitez-pas à me laisser des commentaires ou me poser des questions si le cœur vous en dit, je suis toujours ouvert pour discuter et même me faire remettre à l’ordre 🙂