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 🙂

Interface | la base

Quand on m’a enseigné au cégep qu’est-ce qu’une interface, j’ai compris qu’une fois que l’on implémentait la dite interface dans une classe, c’était comme un contrat. Pour moi, le fait de mettre le nom de l’interface après le nom de la classe séparé par ‘:’ devenait un contrat qui exigeait que l’on implémente tous les éléments de l’interface.

Je n’arrivais pas à comprendre pourquoi est-ce qu’il fallait que je me passe un contrat à moi-même qui exige d’écrire des bouts de code dont je suis l’auteur et que forcément j’allais écrire.

J’étais bien loin du compte et je n’avais encore rien vue ni compris de ce que peux faire une interface et je n’avais surtout pas encore capté toute la beauté de la chose.

Alors voilà, au fil du temps et mes programmes de formations continue m’ont amené à finalement comprendre comment ça fonctionne. Je prendrai ici le temps de vous l’expliquer si vous êtes comme moi il y a un certain temps.

La base

Pour commencer, je commencerai par vous expliquer qu’est-ce qu’une interface en utilisant une analogie.

Imaginez-vous une interface comme un masque représentant un personnage de théâtre qu’un humain peut revêtir. Une fois le masque revêtu, vous devez jouer le rôle, peu importe qui vous êtes vraiment et surtout, peu importe toute les autres choses que vous pouvez penser, dire ou être derrière le masque.

De la même manière que l’humain revêt le masque et joue son rôle, une classe qui implémente une interface se doit de jouer le rôle programmé dans l’interface, peu importe tout ce qu’elle peut faire d’autre de manière publique ou privée.

Ce n’est donc pas une question de contrat comme je pouvais me l’imaginer au départ, mais une question de rôle ou de fonctionnalité si on peut dire.

Alors comment est-ce que cela se concrétise dans le code et pourquoi aurais-je besoin de ça ?

Implémenter une interface

Avant d’implémenter une interface, il faut d’abord la créer et c’est ce que je ferai ici en utilisant des exemples qui seront écrits en C#.

D’abord établissons le rôle que l’on veut attribuer à une classe. Disons que le rôle consiste en une méthode qui additionne deux entiers et à l’existence d’une propriété qui prend la valeur de l’addition. C’est un peu fou comme exemple, mais c’est uniquement pour la démonstration et pour montrer qu’on peut avoir plus que des méthodes dans une interface.

Alors nous créons l’interface comme suit :

namespace InterfacePlay { public interface IFunAddition { int Addition(int x, int y); public int Resultat { get; set; } } }

Maintenant que notre rôle a été déterminé, nous allons faire en sorte qu’une classe le joue de cette manière en ajoutant le nom de l’interface après le nom de la classe et en implémentant les éléments de l’interface comme suit :

public class FunAddition : IFunAddition { public int Resultat { get; set; } public FunAddition(int x, int y) { Addition(x, y); } private void Addition(int x, int y) { Resultat = x + y; } }

Là où je voudrais attirer votre attention, c’est sur la différence entre l’interface et la classe. La classe possède bien l’implémentation des deux éléments de l’interface en plus d’une méthode private qui additionne les deux entiers. Donc, la classe est prête à jouer le rôle, mais comme vous pouvez voir, elle peut le faire à sa manière comme l’existence de la méthode private le démontre, sans que cela change son rôle.

Voilà donc une des choses importantes quand une classe implémente une interface, c’est qu’elle le fait à sa manière tout en cachant bien comment elle le fait ainsi que ses autres éléments, même s’ils sont publics. J’aurais bien pu changer la portée de la méthode Addition pour public une fois le rôle revêtu (ou l’interface implémentée) que cela ne changerais rien. Vous verrez pourquoi un peu plus loin.

Voyons donc comment cela s’utilise en pratique et où cela devient intéressant.

Utiliser une interface

Dans cette partie, pour faire une mise en scène un peu plus vrai, on va simuler que l’on fait une partie de code pour un client REST. On va d’abord utiliser une classe concrète pour le client REST pour fin de comparaison, ensuite nous emploierons une interface pour interagir avec les fonctionnalités de la classe.

Le code utilisé pour les exemples provient d’un projet que j’ai fait sur github. Vous pouvez donc vous référer au projet complet si vous en sentez le besoin.

La classe REST pour le client

La classe REST n’en est pas une vraie. On va simuler que c’est une vraie classe, car à un moment, elle s’identifie en donnant une valeur à un événement comme suit « RestClient » dans une propriété d’un événement RestServerResponded qu’elle retourne. Mais, pour la démonstration, nous la considérerons comme un vrai client REST.

using System.Net; namespace InterfacePlay { public class RestClient { public void AddRestRequest(RestRequest restRequest) { MakeRealCallWithRestServer(restRequest); } public event OnRestServerResponded RestServerResponded; private void MakeRealCallWithRestServer(RestRequest restRequest) { var status = MakeRequest(restRequest); SendStatusToCaller(status); } private void SendStatusToCaller(HttpStatusCode status) { var restEvent = new RestEvent() { HttpStatusCode = status, RestClientName = "RestClient" }; RestServerResponded?.Invoke(this, restEvent); } private HttpStatusCode MakeRequest(RestRequest restRequest) => HttpStatusCode.OK; } }

La classe qui utilise le client REST

Nous avons donc ici une classe qui utilise le client REST afin que ce dernier fasse une requête et retourne quelque chose par la suite par un événement RestServerResponded.

using System.Net;

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

        private RestClient restClient = new RestClient();

        public ConsumerSansInterface()
        {
            InitializeComponent();
            SendRequest();
        }

        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()
        {
            restClient.RestServerResponded += (sender, restEvent) =>
            {
                HttpStatusCode = restEvent.HttpStatusCode;
                RestClientName = restEvent.RestClientName;
            };
        }
    }
}

La ligne importante à comprendre est la suivante :

        private RestClient restClient = new RestClient();

Nous avons donc un schéma standard d’une classe qui en utilise un autre en déclarant et manipulant directement un objet de cette autre classe. C’est un couplage normal si on peut dire. Si on fait un test comme vous pouvez en trouver sur le projet « ConsumerSansInterfaceTest« , vous pourriez vous rendre compte que la classe utilisée est bien RestClient en débuguant ou en évaluant la valeur de la propriété RestClientName retournée par l’événement RestServerResponded. Maintenant faisons la même chose en utilisant une interface.

Utiliser le client à travers une interface

À présent, nous ferons presque la même chose que dans le schéma standard, mais cette fois-ci, nous implémenterons l’interface IRestClient dans la classe RestClient en premier lieu. Ensuite, à partir de la classe qui utilise le client REST, nous déclarerons une interface de type IRestClient et ce sera avec cette interface que nous interagirons.

Le schéma sera très simple pour la démonstration. Ce que nous ferons, c’est que nous déclarerons une variable de type IRestClient et nous y collerons un objet de la classe RestClient derrière pour jouer le rôle défini par l’interface. À aucun moment, la classe ConsumerAvecInterface n’utilise la classe RestClient directement. ConsumerAvecInterface passe par l’interface IRestClient.

RestClient implémente IRestClient

La classe implémente l’interface IRestClient en ajoutant le nom de l’interface après la déclaration de la classe et parce qu’elle possède et implémente tous les éléments de l’interface.

using System.Net;

namespace InterfacePlay
{
    public class RestClient : IRestClient
    {
        public void AddRestRequest(RestRequest restRequest)
        {
            MakeRealCallWithRestServer(restRequest);
        }

        public void HelloWorld() {string h = "HelloWorld";}

        public event OnRestServerResponded RestServerResponded;

        private void MakeRealCallWithRestServer(RestRequest restRequest)
        {
            var status = MakeRequest(restRequest);
            SendStatusToCaller(status);
        }

        private void SendStatusToCaller(HttpStatusCode status)
        {
            var restEvent = new RestEvent()
            {
                HttpStatusCode = status,
                RestClientName = "RestClient"
            };
            RestServerResponded?.Invoke(this, restEvent);
        }

        private HttpStatusCode MakeRequest(RestRequest restRequest) => HttpStatusCode.OK;
    }
}

Comme vous pouvez le voir, la seule différence entre cette classe RestClient et l’autre que nous avons vu dans l’exemple de schéma standard, c’est le nom de l’interface collé à côté du nom de la classe. C’est pour dire que ce n’est pas difficile d’implémenter une interface « généralement ».

La classe a un élément d’ajouté que l’interface ne possède pas : la méthode public HelloWorld. Cela sert à démontrer que la classe peut posséder ses propres méthodes, variables ou autres éléments qui ne sont pas obligés d’être connus par l’interface et que cela fonctionne quand même. On y reviendra plus tard pour cette méthode.

Classe ConsumerAvecInterface

Ce sera presque exactement la même chose que pour l’autre classe ConsumerSansInterface à l’exception que nous déclarerons une variable de type IRestClient et que nous y collerons un objet RestClient derrière pour jouer le rôle de l’interface IRestClient.

using System.Net;

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

        private IRestClient restClient = new RestClient();

        public ConsumerAvecInterface()
        {
            InitializeComponent();
            SendRequest();
        }

        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()
        {
            restClient.RestServerResponded += (sender, restEvent) =>
            {
                HttpStatusCode = restEvent.HttpStatusCode;
                RestClientName = restEvent.RestClientName;
            };
        }
    }
}

Voyez, le détail qui nous importe ici c’est la ligne suivante :

private IRestClient restClient = new RestClient();

On déclare une variable de type IRestClient et on y colle un objet RestClient derrière pour jouer le rôle de l’interface. À aucun moment la classe ConsumerAvecInterface ne sait avec quel objet elle interagit, comment il implémente les éléments de l’interface IRestClient ni si l’objet de la classe RestClient ne possède d’autres éléments publics. D’ailleurs, à ce sujet, si vous essayez d’effectuer d’exécuter la méthode publique HelloWorld de RestClient à partir de ConsumerAvecInterface, vous aurez une erreur de l’Intellisense de Visual Studio.

Au stade où nous en sommes, vous devez probablement trouver que d’implémenter une interface de cette manière n’apporte aucun avantage et ne représente que du code supplémentaire à ajouter. Vous avez raison parce que c’est seulement la base que nous avons vue ici. L’implémentation d’une interface est un tantinet plus complexe dans une solution réelle, mais apporte tellement beaucoup plus d’avantage que de désagrément.

C’est ce que nous verrons donc dans les prochains articles que je mettrai en ligne ici. Nous verrons entre autres un « design pattern »: la fabrique de classe. Ensuite, un des éléments des principes SOLID: l’injection de dépendance. Tout cela se fera en montrant comment ces principes peuvent rendre vos tests unitaires beaucoup plus fiables et facile à écrire.

En conclusion

On a d’abord vu comment créer une interface et comment l’implémenter dans une classe en C#.

Ensuite, on a vue comment utiliser un objet à partir d’une classe dans un schéma standard ou l’on couple un objet à un autre.

C’est après que l’on a vu comment faire la même chose, mais en utilisant une interface au lieu d’un objet et que l’on a compris que l’on interagissait maintenant avec une variable qui contient un rôle plutôt qu’avec un objet de classe réel.

D’autres articles sur les interfaces suivront et, je vous le promets, nous passerons à un autre niveau.

Merci pour votre intérêt. Ne vous gênez pas pour me corriger ou pour me dire combien cet article vous a aidé.