Comment utiliser l'inversion de contrôle en C #

L'inversion du contrôle et l'injection de dépendances vous permettent de rompre les dépendances entre les composants de votre application et de faciliter le test et la maintenance de votre application. Cependant, l'inversion du contrôle et l'injection de dépendances ne sont pas les mêmes - il existe des différences subtiles entre les deux.

Dans cet article, nous allons examiner l'inversion du modèle de contrôle et comprendre en quoi elle diffère de l'injection de dépendances avec des exemples de code pertinents en C #.

Pour utiliser les exemples de code fournis dans cet article, vous devez disposer de Visual Studio 2019 installé sur votre système. Si vous n'en avez pas déjà une copie, vous pouvez télécharger Visual Studio 2019 ici. 

Créer un projet d'application console dans Visual Studio

Tout d'abord, créons un projet d'application console .NET Core dans Visual Studio. En supposant que Visual Studio 2019 est installé sur votre système, suivez les étapes décrites ci-dessous pour créer un nouveau projet d'application console .NET Core dans Visual Studio.

  1. Lancez l'IDE de Visual Studio.
  2. Cliquez sur "Créer un nouveau projet".
  3. Dans la fenêtre «Créer un nouveau projet», sélectionnez «Application console (.NET Core)» dans la liste des modèles affichés.
  4. Cliquez sur Suivant. 
  5. Dans la fenêtre «Configurer votre nouveau projet» ci-dessous, spécifiez le nom et l'emplacement du nouveau projet.
  6. Cliquez sur Créer. 

Cela créera un nouveau projet d'application console .NET Core dans Visual Studio 2019. Nous utiliserons ce projet pour explorer l'inversion de contrôle dans les sections suivantes de cet article.

Qu'est-ce que l'inversion de contrôle?

L'inversion de contrôle (IoC) est un modèle de conception dans lequel le flux de contrôle d'un programme est inversé. Vous pouvez profiter de l'inversion du modèle de contrôle pour découpler les composants de votre application, permuter les implémentations de dépendances, simuler des dépendances et rendre votre application modulaire et testable.

L'injection de dépendance est un sous-ensemble de l'inversion du principe de contrôle. En d'autres termes, l'injection de dépendances n'est qu'une façon d'implémenter l'inversion de contrôle. Vous pouvez également implémenter l'inversion de contrôle à l'aide d'événements, de délégués, d'un modèle de modèle, d'une méthode de fabrique ou d'un localisateur de services, par exemple.

L'inversion du modèle de conception de contrôle indique que les objets ne doivent pas créer d'objets dont ils dépendent pour effectuer une activité. Au lieu de cela, ils devraient obtenir ces objets d'un service extérieur ou d'un conteneur. L'idée est analogue au principe hollywoodien qui dit: "Ne nous appelez pas, nous vous appellerons." Par exemple, au lieu que l'application appelle les méthodes dans un framework, le framework appelle l'implémentation qui a été fournie par l'application. 

Exemple d'inversion de contrôle en C #

Supposons que vous construisez une application de traitement des commandes et que vous souhaitez implémenter la journalisation. Par souci de simplicité, supposons que la cible du journal est un fichier texte. Sélectionnez le projet d'application console que vous venez de créer dans la fenêtre Explorateur de solutions et créez deux fichiers, nommés ProductService.cs et FileLogger.cs.

    classe publique ProductService

    {

        privé en lecture seule FileLogger _fileLogger = new FileLogger ();

        public void Log (message sous forme de chaîne)

        {

            _fileLogger.Log (message);

        }

    }

    classe publique FileLogger

    {

        public void Log (message sous forme de chaîne)

        {

            Console.WriteLine ("Méthode Inside Log de FileLogger.");

            LogToFile (message);

        }

        private void LogToFile (message sous forme de chaîne)

        {

            Console.WriteLine ("Méthode: LogToFile, Texte: {0}", message);

        }

    }

L'implémentation indiquée dans l'extrait de code précédent est correcte mais il existe une limitation. Vous êtes obligé de consigner les données dans un fichier texte uniquement. Vous ne pouvez en aucun cas enregistrer des données dans d'autres sources de données ou des cibles de journal différentes.

Une implémentation inflexible de la journalisation

Et si vous vouliez enregistrer des données dans une table de base de données? L'implémentation existante ne prendrait pas en charge cela et vous seriez obligé de changer l'implémentation. Vous pouvez modifier l'implémentation de la classe FileLogger ou créer une nouvelle classe, disons DatabaseLogger.

    public class DatabaseLogger

    {

        public void Log (message sous forme de chaîne)

        {

            Console.WriteLine ("Méthode Inside Log de DatabaseLogger.");

            LogToDatabase (message);

        }

        private void LogToDatabase (message sous forme de chaîne)

        {

            Console.WriteLine ("Méthode: LogToDatabase, Texte: {0}", message);

        }

    }

Vous pouvez même créer une instance de la classe DatabaseLogger dans la classe ProductService comme indiqué dans l'extrait de code ci-dessous.

classe publique ProductService

    {

        privé en lecture seule FileLogger _fileLogger = new FileLogger ();

        privé en lecture seule DatabaseLogger _databaseLogger =

         nouveau DatabaseLogger ();

        public void LogToFile (message sous forme de chaîne)

        {

            _fileLogger.Log (message);

        }

        public void LogToDatabase (message sous forme de chaîne)

        {

            _fileLogger.Log (message);

        }

    }

Cependant, même si cela fonctionnerait, que se passerait-il si vous deviez enregistrer les données de votre application dans EventLog? Votre conception n'est pas flexible et vous serez obligé de modifier la classe ProductService chaque fois que vous devez vous connecter à une nouvelle cible de journal. Ce n'est pas seulement encombrant, mais il vous sera également extrêmement difficile de gérer la classe ProductService au fil du temps.

Ajoutez de la flexibilité avec une interface 

La solution à ce problème est d'utiliser une interface que les classes de journalisation concrètes implémenteraient. L'extrait de code suivant montre une interface appelée ILogger. Cette interface serait implémentée par les deux classes concrètes FileLogger et DatabaseLogger.

interface publique ILogger

{

    void Log (message de chaîne);

}

Les versions mises à jour des classes FileLogger et DatabaseLogger sont données ci-dessous.

classe publique FileLogger: ILogger

    {

        public void Log (message sous forme de chaîne)

        {

            Console.WriteLine ("Méthode Inside Log de FileLogger.");

            LogToFile (message);

        }

        private void LogToFile (message sous forme de chaîne)

        {

            Console.WriteLine ("Méthode: LogToFile, Texte: {0}", message);

        }

    }

classe publique DatabaseLogger: ILogger

    {

        public void Log (message sous forme de chaîne)

        {

            Console.WriteLine ("Méthode Inside Log de DatabaseLogger.");

            LogToDatabase (message);

        }

        private void LogToDatabase (message sous forme de chaîne)

        {

            Console.WriteLine ("Méthode: LogToDatabase, Texte: {0}", message);

        }

    }

Vous pouvez désormais utiliser ou modifier l'implémentation concrète de l'interface ILogger chaque fois que nécessaire. L'extrait de code suivant montre la classe ProductService avec une implémentation de la méthode Log.

classe publique ProductService

    {

        public void Log (message sous forme de chaîne)

        {

            ILogger logger = nouveau FileLogger ();

            logger.Log (message);

        }

    }

Jusqu'ici tout va bien. Cependant, que se passe-t-il si vous souhaitez utiliser le DatabaseLogger à la place du FileLogger dans la méthode Log de la classe ProductService? Vous pouvez modifier l'implémentation de la méthode Log dans la classe ProductService pour répondre à l'exigence, mais cela ne rend pas la conception flexible. Rendons maintenant la conception plus flexible en utilisant l'inversion de contrôle et l'injection de dépendances.

Inverser le contrôle à l'aide de l'injection de dépendances

L'extrait de code suivant illustre comment vous pouvez tirer parti de l'injection de dépendances pour transmettre une instance d'une classe de journalisation concrète à l'aide de l'injection de constructeur.

classe publique ProductService

    {

        privé en lecture seule ILogger _logger;

        ProductService public (enregistreur ILogger)

        {

            _logger = enregistreur;

        }

        public void Log (message sous forme de chaîne)

        {

            _logger.Log (message);

        }

    }

Enfin, voyons comment nous pouvons transmettre une implémentation de l'interface ILogger à la classe ProductService. L'extrait de code suivant montre comment créer une instance de la classe FileLogger et utiliser l'injection de constructeur pour transmettre la dépendance.

static void Main (string [] args)

{

    ILogger logger = nouveau FileLogger ();

    ProductService productService = nouveau ProductService (enregistreur);

    productService.Log ("Hello World!");

}

Ce faisant, nous avons inversé le contrôle. La classe ProductService n'est plus chargée de créer une instance d'une implémentation de l'interface ILogger ou même de décider quelle implémentation de l'interface ILogger doit être utilisée.

L'inversion de contrôle et l'injection de dépendances vous aident dans l'instanciation automatique et la gestion du cycle de vie de vos objets. ASP.NET Core comprend une simple inversion intégrée du conteneur de contrôle avec un ensemble limité de fonctionnalités. Vous pouvez utiliser ce conteneur IoC intégré si vos besoins sont simples ou utiliser un conteneur tiers si vous souhaitez exploiter des fonctionnalités supplémentaires.

Vous pouvez en savoir plus sur la façon de travailler avec l'inversion de contrôle et l'injection de dépendances dans ASP.NET Core dans mon article précédent ici.