web-dev-qa-db-fra.com

Qu'est-ce que Ninject et quand l'utilisez-vous?

J'ai aidé quelques amis sur un projet et il y a une classe qui utilise Ninject. Je suis assez nouveau en C # et je n'ai aucune idée de ce que fait cette classe, c'est pourquoi j'ai besoin de comprendre Ninject. Quelqu'un peut-il expliquer ce qu'est Ninject et quand peut-on l'utiliser (avec un exemple si possible)? Ou si vous pouvez pointer vers certains liens, ce serait bien aussi.

J'ai essayé cette question: Tutoriels/documentations Ninject? mais cela n'a pas vraiment aidé un débutant comme moi.

56
bachkoi32

Ninject est un injecteur de dépendances pour .NET, une réalisation pratique de l'injection de dépendance de modèle (forme de modèle d'inversion de contrôle).

Supposons que vous ayez deux classes DbRepository et Controller:

class Controller {
   private DbRepository _repository;

   // ... some methods that uses _repository
}

class DbRepository {
   // ... some bussiness logic here ...
}

Donc, maintenant vous avez deux problèmes:

  1. Vous devez inialiser _repository Pour l'utiliser. Vous avez de telles façons:

    1. Dans le constructeur manuellement. Mais que se passe-t-il si le constructeur de DbRepository change? Vous devez réécrire votre classe Controller car une autre partie du code a été modifiée. Ce n'est pas difficile si vous n'avez qu'un seul Controller, mais si vous avez quelques classes qui dépendent de votre Repository, vous avez un vrai problème.
    2. Vous pouvez utiliser le localisateur de service ou l'usine ou smth. Maintenant, vous dépendez de votre localisateur de services. Vous disposez d'un localisateur de service global et tout le code doit l'utiliser. Comment allez-vous changer le comportement du localisateur de services lorsque vous devez utiliser dans une partie du code une logique d'activation et quelque chose d'autre dans une autre partie du code? Il n'y a qu'une seule façon: passer le localisateur de services via les constructeurs. Mais avec de plus en plus de cours, vous devrez le passer de plus en plus. Quoi qu'il en soit, c'est une bonne idée, mais c'est une mauvaise idée.

      class Controller {
         private DbRepository _repository;
      
         public Controller() {
           _repository = GlobalServiceLocator.Get<DbRepository>()
         }
      
         // ... some methods that uses _repository
      }
      
    3. Vous pouvez utiliser l'injection de dépendance. Regardez le code:

      class Controller {
         private IRepository _repository;
      
         public Controller(IRepository repository) {
            _repository = repository;
         }
      }
      

    Maintenant, lorsque vous avez besoin de votre contrôleur, vous écrivez: ninjectDevKernel.Get<Controller>(); ou ninjectTestKernel.Get<Controller>();. Vous pouvez basculer entre les résolveurs de dépendances aussi rapidement que vous le souhaitez. Voir? C'est simple, vous n'avez pas besoin d'écrire beaucoup.

  2. Vous ne pouvez pas faire de tests unitaires dessus. Votre Controller dépend de DbRepository et si vous voulez tester une méthode qui utilise le référentiel, votre code ira à la base de données et lui demandera des données. C'est lent, très lent. Si votre code dans DbRepository change, votre test unitaire sur Controller tombera. Seul le test d'intégration doit dire "problèmes" dans ce cas. Ce dont vous avez besoin dans les tests unitaires - pour isoler vos classes et tester une seule classe dans un test (dans l'idéal - une seule méthode). Si votre code DbRepository échoue, vous penserez que le code Controller a échoué - et c'est mauvais (même si vous avez des tests pour DbRepository et Controller - les deux échouera et vous pouvez commencer au mauvais endroit). Il faut beaucoup de temps pour déterminer où se trouve vraiment l'erreur. Vous devez savoir qu'une classe est correcte et qu'une autre classe a échoué.

  3. Lorsque vous voulez remplacer DbRepository par quelque chose d'autre dans toutes vos classes, vous devez faire beaucoup de travail.

  4. Vous ne pouvez pas contrôler facilement la durée de vie de DbRepository. L'objet de cette classe crée lors de l'inialisation de Controller et supprime lorsque Controller est supprimé. Il n'y a pas de partage entre différentes instances de la classe Controller et il n'y a pas de partage entre d'autres classes. Avec Ninject, vous pouvez simplement écrire:

    kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
    

Et la particularité de l'injection de dépendances - le développement agile! Vous décrivez que votre contrôleur utilise un référentiel avec l'interface IRepository. Vous n'avez pas besoin d'écrire DbRepository, vous pouvez écrire une classe simple MemoryRepository et développer Controller tandis que d'autres gars développent DbRepository. Lorsque DbRepository sera terminé, il vous suffit de relier dans votre résolveur de dépendances que IRepository par défaut est DbRepository maintenant. Vous avez écrit beaucoup de contrôleurs? Tous utiliseront maintenant DbRepository. C'est super.

Lire la suite:

  1. Inversion de contrôle (wiki)
  2. Injection de dépendances (wiki)
  3. Inversion des conteneurs de contrôle et schéma d'injection de dépendance (Martin Fowler)
46
Viktor Lova

Ninject est un conteneur d'inversion de contrôle.

Qu'est ce que ça fait?

Supposons que vous ayez une classe Car qui dépend d'une classe Driver.

public class Car 
{
   public Car(IDriver driver)
   {
      ///
   }
}

Pour utiliser la classe Car, vous la construisez comme suit:

IDriver driver = new Driver();
var car = new Car(driver);

Un conteneur IoC centralise les connaissances sur la création de classes. Il s'agit d'un référentiel central qui connaît certaines choses. Par exemple, il sait que la classe concrète que vous devez utiliser pour construire une voiture est un Driver et pas un autre IDriver.

Par exemple, si vous développez une application MVC, vous pouvez indiquer à Ninject comment construire vos contrôleurs. Pour ce faire, enregistrez les classes concrètes qui satisfont des interfaces spécifiques. Au moment de l'exécution, Ninject déterminera quelles classes sont nécessaires pour construire le contrôleur requis, et tout cela dans les coulisses.

// Syntax for binding
Bind<IDriver>().To<Driver>();

Ceci est avantageux car il vous permet de créer des systèmes qui sont plus facilement testables à l'unité. Supposons que Driver encapsule tous les accès à la base de données pour Car. Dans un test unitaire pour Car, vous pouvez faire ceci:

IDriver driver = new TestDriver(); // a fake driver that does not go to the db
var car = new Car(driver);

Il existe des frameworks entiers qui s'occupent de créer automatiquement des classes de test pour vous et ils sont appelés frameworks de simulation.

Pour plus d'informations:

41
Sklivvz

D'autres réponses sont excellentes, mais je voudrais également souligner cet article Implémentation de l'injection de dépendances à l'aide de Ninject .
C'est l'un des meilleurs articles que j'ai jamais lu qui explique l'Injection de dépendance et Ninject avec un exemple très élégant.

Voici l'extrait de l'article:

L'interface ci-dessous sera implémentée par nos (SMSService) et (MockSMSService), fondamentalement la nouvelle interface (ISMSService) exposera les mêmes comportements des deux services que le code ci-dessous:

public interface ISMSService
 {
 void SendSMS(string phoneNumber, string body);
 }

Implémentation (SMSService) pour implémenter l'interface (ISMSService):

public class SMSService : ISMSService
 {
 public void SendSMS(string mobileNumber, string body)
 {
 SendSMSUsingGateway(mobileNumber, body);
 }




private void SendSMSUsingGateway(string mobileNumber, string body)
 {
 /*implementation for sending SMS using gateway*/
Console.WriteLine("Sending SMS using gateway to mobile: 
    {0}. SMS body: {1}", mobileNumber, body);
 }
 }

(MockSMSService) avec une implémentation totalement différente utilisant la même interface:

public class MockSMSService :ISMSService
 {
 public void SendSMS(string phoneNumber, string body)
 {
 SaveSMSToFile(phoneNumber,body);
 }

private void SaveSMSToFile(string mobileNumber, string body)
 {
 /*implementation for saving SMS to a file*/
Console.WriteLine("Mocking SMS using file to mobile: 
    {0}. SMS body: {1}", mobileNumber, body);
 }
 }

nous devons implémenter une modification de notre constructeur de classe (UIHandler) pour passer la dépendance à travers elle, ce faisant, le code qui utilise le (UIHandler) peut déterminer quelle implémentation concrète de (ISMSService) utiliser:

public class UIHandler
 {
 private readonly ISMSService _SMSService;

public UIHandler(ISMSService SMSService)
 {
 _SMSService = SMSService;
 }
 public void SendConfirmationMsg(string mobileNumber) {

 _SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
 }
 }

Maintenant, nous devons créer une classe distincte (NinjectBindings) qui hérite de (NinjectModule). Cette classe sera chargée de résoudre les dépendances au moment de l'exécution, puis nous remplacerons l'événement de chargement utilisé pour configurer la liaison. La bonne chose à propos de Ninject est que nous n'avons pas besoin de changer notre code dans (ISMSService), (SMSService) et (MockSMSService).

public class NinjectBindings : Ninject.Modules.NinjectModule
 {
 public override void Load()
 {
 Bind<ISMSService>().To<MockSMSService>();
 }
 }

Maintenant, dans le code du formulaire d'interface utilisateur, nous allons utiliser la liaison pour Ninject qui déterminera quelle implémentation utiliser:

class Program
 {
 static void Main(string[] args)
 {
 IKernel _Kernal = new StandardKernel();
 _Kernal.Load(Assembly.GetExecutingAssembly());
 ISMSService _SMSService = _Kernal.Get<ISMSService>();

UIHandler _UIHandler = new UIHandler(_SMSService);
 _UIHandler.SendConfirmationMsg("96279544480");

Console.ReadLine();
 }
 }

Maintenant, le code utilise le Ninject Kernal pour résoudre toutes les chaînes de dépendances.Si nous voulons utiliser le service réel (SMSService) en mode Release (sur l'environnement de production) au lieu du modèle fictif, nous devons changer la classe de liaison Ninject ( NinjectBindings) uniquement pour utiliser la bonne implémentation ou en utilisant la directive #if DEBUG comme ci-dessous:

public class NinjectBindings : Ninject.Modules.NinjectModule
 {
 public override void Load()
 {
#if DEBUG
 Bind<ISMSService>().To<MockSMSService>();
#else
 Bind<ISMSService>().To<SMSService>();
#endif

}
 }

Maintenant, notre classe de liaison (NinjectBindings) vit au dessus de tout notre code d'exécution et nous pouvons contrôler facilement la configuration en une seule fois.


Voir aussi Qu'est-ce que l'inversion de contrôle? quelques exemples très simples sont mentionnés pour comprendre l'IoC.

6
JerryGoyal