Accueil > Ninject, from Zero to Hero – Part 1
Philippe Lorieul
3 septembre 2015

Ninject, from Zero to Hero – Part 1

Ninject, from Zero to Hero - Part 1

Depuis l’émergence du mouvement Craft, le Clean Code et les principes SOLID sont à la mode. Tout développeur qui se respect doit produire du code maintenable, testable et réutilisable. Dans cet article, nous allons vous présenter Ninject, un outil dont le but est de faciliter la production d’un tel code. Simple d’utilisation, ce framework modulable se démarque par son interface fluent, de nombreuses fonctionnalités et un grand nombre d’extensions. Après ces quelques pages, vous aurez utilisé Ninject pour faire de l’injection, de l’injection conditionnée, des singletons, de l’AOP, … Vous aurez toutes les armes pour créer vos applications avec Ninject.

Dépendance et couplage

Nos programmes sont composés de classes qui « communiquent » entre elles pour exécuter des tâches particulières. Cette communication implique qu’une classe peut avoir besoin d’une autre pour faire son travail. Lorsqu’une classe A a besoin d’une classe B pour fonctionner, on dit que B est une dépendance de A. Le degré de dépendance entre deux composants logiciels est nommé couplage.
Pour illustrer le concept, nous allons prendre l’exemple d’une fonctionnalité utilisée dans la plupart de nos applications : le logging.

using System;

namespace GreetingsGenerator
{
    public class GreetingsGenerator
    {
        private ConsoleLogger logger;

        public GreetingsGenerator()
        {
            logger = new ConsoleLogger();
        }

        public void Greet(string name)
        {
            logger.Log(String.Format("Greeting {0}", name));
            Console.WriteLine("Hello {0}", name);
        }
    }
}

Dans cet extrait de code, la classe GreetingsGenerator a besoin de la classe Logger pour fonctionner : la classe Logger est une dépendance de la classe GreetingsGenerator. Les dépendances en tant que telles ne posent pas de problème. A vrai dire, on ne peut pas s’en passer. Cela dit, la manière de coder ces dépendances dans nos programmes rend nos logiciels plus ou moins maintenables et évolutifs. Dans l’exemple ci-dessus, le Logger est instancié au sein même de la classe qui l’utilise. Ceci crée un couplage fort entre ces classes : on ne peut pas utiliser la classe GreetingsGenerator sans la classe Logger et on ne peut pas changer de logger sans modifier la classe GreetingsGenerator.

 

Inversion de contrôle et injection de dépendances

L’inversion de contrôle (IoC) est une technique dont le but est de réduire le couplage entre les composants logiciels. Pour ce faire, les coutures entre les composants logiciels ne sont plus faites dans l’application elle-même, mais par un composant tiers (souvent un framework) jouant le rôle d’orchestrateur.
L’injection de dépendances, ou DI, est une méthode d’IoC. Le principe consiste à « passer » les dépendances aux classes qui les utilisent en évitant les instanciations (les « new ») au sein des classes dépendantes, augmentant ainsi la souplesse du programme.
Réécrivons la classe GreetingsGenerator en utilisant l’injection de dépendances

using System;

namespace GreetingsGenerator
{
    public class GreetingsGenerator
    {
        private ILogger logger;

        public GreetingsGenerator(ILogger logger)
        {
            this.logger = logger;
        }

        public void Greet(string name)
        {
            logger.Log(String.Format("Greeting {0}", name));
            Console.WriteLine("Hello {0}", name);
        }
    }
}

 

Pour réduire le couplage entre les deux classes, nous avons fait deux choses. Premièrement, nous supprimons l’instanciation de la dépendance du service. Cette dernière est maintenant passée par constructeur. Ensuite, la classe GreetingsGenerator ne dépend plus directement d’un type spécifique de logger (ConsoleLogger), mais d’une interface. Ceci nous permet d’utiliser n’importe quelle implémentation de logger (Console, fichier, syslog, event windows, etc…). La classe GreetingsGenerator est maintenant beaucoup plus autonome. Elle a besoin d’un service de log mais les détails d’implémentation ne la concernent plus.
C’est à présent dans le code client de la classe GreetingsGenerator que se passe la configuration (IoC)

using System;

namespace GreetingsGenerator
{
    class Program
    {
        static void Main(string[] args)
        {
            var logger = new ConsoleLogger();
            var generator = new GreetingsGenerator(logger);
            generator.Greet("John Doe");
            Console.Read();
        }
    }
}

Notez qu’ici il serait très simple de changer le type de logger passé à la classe dépendante.
var logger = new FileLogger() ;

 

Ninject, utilisation simple (cas nominal)

Ninject est un framework d’IoC. Bien qu’il soit possible en pratique de gérer la configuration de vos dépendances « à la main » comme nous venons de le faire, la tâche devient vite laborieuse dans nos applications d’entreprise où les relations de dépendances peuvent être très complexes.
Les Conteneurs IoC permettent de centraliser la configuration des dépendances de vos classes. Lorsque vous utilisez Ninject, vous pouvez indiquer à un seul endroit de votre application, que ce soit un fichier de configuration XML ou une classe, quelles implémentations injecter à vos classes. Dans le monde Ninject, l’association entre une implémentation et une interface s’appelle un Binding.
L’installation de Ninject dans vos projets se fait très simplement, à l’aide du gestionnaire de package Nuget.
Une fois le package installé, on peut configurer nos dépendances dans le code. La partie de l’application où est placée cette configuration s’appelle la racine de composition. Elle est classiquement située au plus près du point d’entrée du programme (fonction Main pour application console, global.asax pour une application web, etc…).

using System;
using Ninject;

namespace GreetingsGenerator
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var kernel = new StandardKernel())
            {
                kernel.Bind<ILogger>().To<ConsoleLogger>();                   
                var generator = kernel.Get<GreetingsGenerator>();
                generator.Greet("John Doe");
                Console.Read();
            }
        }
    }
}

 

Regardons ce code de plus près. Il se déroule en 3 temps :
Récupération d’une référence à Ninject : L’objet kernel représente le Conteneur IoC. C’est l’origine du graphe de dépendances. Lorsque l’on veut demander quelque chose à Ninject, on utilise le kernel. Il existe plusieurs types de kernels, mais le kernel standard suffit dans la majorité des cas.
Configuration des Bindings : une fois que l’on a une référence au noyau de Ninject, on peut configurer les implémentations associées aux interfaces. La ligne kernel.Bind().To() fait exactement ça : elle dit à Ninject “Dès qu’une classe a besoin d’un ILogger, passe lui une instance de ConsoleLogger.”
Récupération de l’instance de service : Au lieu d’instancier directement notre service, nous demandons à Ninject de le faire. kernel.Get<GreetingsGenerator().
Comme nous avons préalablement dit à Ninject quel type concret associer à la dépendance ILogger du service, Ninject peut nous fournir l’instance sans problème.

La suite dans un prochain article…

Nos autres articles
Commentaires
Laisser un commentaire

Restez au courant des dernières actualités !
Le meilleur de l’actualité sur le Cloud, le DevOps, l’IT directement dans votre boîte mail.