What is Ninject and when do you use it?

假如想象 提交于 2019-12-18 10:10:46

问题


I have been helping a few friends on a project and there is a class that uses Ninject. I am fairly new to C# and I have no idea what that class is doing, which is why I need to understand Ninject. Can anyone explain what Ninject is and when does one use it(with example if possible)? Or if you can point to some links that would be great too.

I tried this question: Ninject tutorials/documentations? but it didn't really help a beginner like me.


回答1:


Ninject is dependency injector for .NET, practical realisation of pattern Dependency Injection (form of Inversion of Control pattern).

Suppose you have two classes DbRepository and Controller:

class Controller {
   private DbRepository _repository;

   // ... some methods that uses _repository
}

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

So, now you have two problems:

  1. You must inialize _repository to use it. You have such ways:

    1. In constructor manually. But what if constructor of DbRepository changes? You need to rewrite your Controller class because other part of code was changed. It's not hard if you have only one Controller, but if you have a couple of classes that have dependency on your Repository you have a real problem.
    2. You can use service locator or factory or smth. Now you have dependency on your service locator. You have a global service locator and all code must use it. How you will change behaviour of service locator when you need to use in one part of code one activation logic and something other in other part of code? There is only one way - passing service locator through the constructors. But with more and more classes you will need to pass it more and more. Anyway, that is nice idea, but it is bad idea.

      class Controller {
         private DbRepository _repository;
      
         public Controller() {
           _repository = GlobalServiceLocator.Get<DbRepository>()
         }
      
         // ... some methods that uses _repository
      }
      
    3. You can use dependency injection. Look at the code:

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

    Now when you need your controller you write: ninjectDevKernel.Get<Controller>(); or ninjectTestKernel.Get<Controller>();. You can switch beetween dependency resolvers as fast as you want. See? It's simple, you don't need to write a lot.

  2. You can't make unit tests on it. Your Controller have dependency on DbRepository and if you want test some method that uses repository, your code will go to database and ask it for data. That's slow, very slow. If your code in DbRepository changes, your unit test on Controller will fall. Only integration test must say 'problems' in this case. What you need in unit tests - to isolate your classes and test only one class in one test (in ideal - only one method). If your DbRepository code fails, you will think that Controller code failed - and that's bad (even if you have tests for DbRepository and Controller - they both will fail and you can start from wrong place). It takes a lot of time to determine where is error really. You need to know that some class is ok, and some other class was failed.

  3. When you want replace DbRepository with something other in all your classes, you have to do a lot of work.

  4. You can't easy control lifetime of DbRepository. Object of this class creates on inializing of Controller and deletes when Controller deletes. There are no sharing beetween different instances of Controller class and there are no sharing beetween other classes. With Ninject you can simply write:

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

And special feature of dependency injection - agile development! You describe that your controller uses repository with interface IRepository. You don't need to write DbRepository, you can write simple MemoryRepository class and develop Controller while other guys develops DbRepository. When DbRepository will be finished, you just rebinds in your dependency resolver that default IRepository is DbRepository now. You have writed a lot of controllers? All of them will use now DbRepository. That's cool.

Read more:

  1. Inversion of control (wiki)
  2. Dependency injection (wiki)
  3. Inversion of Control Containers and the Dependency Injection pattern (Martin Fowler)



回答2:


Ninject is an Inversion of Control container.

What does it do?

Suppose you have a Car class that depends on a Driver class.

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

In order to use the Car class you build it like so:

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

A IoC containter centralizes the knowledge about how to build classes. It is a central repository that knows a few things. For example, it knows that the concrete class that you need to use to build a car is a Driver and not any other IDriver.

For example, if you are developing a MVC application, you can tell Ninject how to build your controllers. You do so by registering which concrete classes satisfy specific interfaces. At run time Ninject will figure out which classes are needed to build the required controller, and all behind the scenes.

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

This is beneficial because it lets you build systems that are more easily unit testable. Suppose that Driver encapsulates all database access for Car. In a unit test for Car you can do this:

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

There are entire frameworks that take care of automatically creating testing classes for you and they are called mocking frameworks.

For more information:

  • GitHub/Ninject Home
  • Inversion of Control
  • Inversion of Control Containers and the Dependency Injection pattern
  • Mock Object



回答3:


Other answers are great but I would also like to point out this Implementing Dependency Injection using Ninject article.
This is one of the best articles I ever read which explains Dependency Injection and Ninject with a very elegant example.

Here's the snippet from the article:

Below Interface will be implemented by our (SMSService) and (MockSMSService), basically the new Interface (ISMSService) will expose the same behaviors of both services as the code below:

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

(SMSService) implementation to implement the (ISMSService) interface:

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) with totally different implementation using the same 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);
 }
 }

we need to implement a change to our (UIHandler) class constructor to pass the dependency through it, by doing this, the code which uses the (UIHandler) can determine which concrete implementation of (ISMSService) to use:

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!");
 }
 }

Now, we have to create a separate class (NinjectBindings) which inherits from (NinjectModule). This class will be responsible to resolve dependencies at run time, then we’ll override the load event which is used to configure the binding in it. The nice thing about Ninject is that we do not need to change our code in (ISMSService), (SMSService), and (MockSMSService).

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

Now in UI form code, we’ll use the binding for Ninject which will determine which implementation to use:

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();
 }
 }

Now the code is using the Ninject Kernal to resolve all chain of dependencies, if we want to use the real service (SMSService) in Release mode (on production environment) instead of the mock one, we need to change on the Ninject binding class (NinjectBindings) only to use the right implementation or by using the #if DEBUG directive as below:

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

}
 }

Now our binding class (NinjectBindings) is living on the top of all our execution code and we can control the configuration easily in once place.


Also, see What is Inversion of Control? some very simple examples are mentioned to understand IoC.



来源:https://stackoverflow.com/questions/17375234/what-is-ninject-and-when-do-you-use-it

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!