59

我一直在帮助几个朋友完成一个项目,并且有一个使用 Ninject 的课程。我对 C# 相当陌生,我不知道该类在做什么,这就是为什么我需要了解 Ninject。谁能解释一下 Ninject 是什么以及什么时候使用它(如果可能的话,举个例子)?或者,如果您可以指向一些也很棒的链接。

我试过这个问题:Ninject 教程/文档?但它并没有真正帮助像我这样的初学者。

4

4 回答 4

49

Ninject 是.NET 的依赖注入器,模式依赖注入(控制模式反转的形式)的实际实现。

假设你有两个类DbRepositoryController

class Controller {
   private DbRepository _repository;

   // ... some methods that uses _repository
}

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

所以,现在你有两个问题:

  1. 您必须初始化_repository才能使用它。你有几个选项可以做到这一点:

    1. 手动,在构造函数中。但是如果 DbRepository 的构造函数发生了变化呢?您需要重写您的Controller类,因为它所依赖的代码已更改。如果你只有一个Controller,这并不难,但如果你有几个依赖于你的类,Repository你就有一个真正的问题。
    2. 您可以使用服务定位器或工厂。但是现在您依赖于您的服务定位器。您有一个全局服务定位器,所有代码都必须使用它。当您需要在代码的一部分中使用服务定位器进行激活逻辑但在代码的另一部分中使用它时,您将如何更改服务定位器的行为?只有一种方法 - 通过构造函数传递服务定位器。但是随着越来越多的课程,您将需要越来越多地通过它。无论如何,这是一个好主意,但从长远来看,这是一个坏主意。

      class Controller {
         private DbRepository _repository;
      
         public Controller() {
           _repository = GlobalServiceLocator.Get<DbRepository>()
         }
      
         // ... some methods that uses _repository
      }
      
    3. 您可以使用依赖注入。看代码:

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

      现在,当您需要控制器时,您可以编写:ninjectDevKernel.Get<Controller>();ninjectTestKernel.Get<Controller>();. 您可以随心所欲地在依赖关系解析器之间切换。看?很简单,不需要写很多。

  2. 你不能为它创建单元测试。你Controller有一个依赖DbRepository,如果你想测试一些使用存储库的方法,你的代码将进入数据库并要求它提供数据。这很慢,非常慢。如果您的代码DbRepository发生更改,您的单元测试Controller将会失败。在这种情况下,只有集成测试必须警告您“问题”。您在单元测试中需要的是隔离您的类并在一次测试中只测试一个类(理想情况下 - 只有一种方法)。如果你的DbRepository代码失败了,你会认为Controller代码失败了——这很糟糕(即使你有测试DbRepositoryController- 他们都会失败,你可以从错误的地方开始)。确定错误的真正位置需要花费大量时间。您需要知道 A 类没问题,而 B 类出现了问题。

  3. 当您想DbRepository在所有课程中用其他东西替换时,您必须做很多工作。

  4. 你不能轻易控制DbRepository. 此类的对象在初始化时创建Controller并在删除时Controller删除。类的不同实例之间Controller没有共享,其他类之间也没有共享。使用 Ninject,您可以简单地编写:

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

依赖注入的一个特点——敏捷开发!您描述了您的控制器使用带有 interface 的存储库IRepository。你不需要写DbRepository,你可以简单地创建一个MemoryRepository类并开发Controller,而另一个人开发DbRepository。工作DbRepository完成后,您只需在依赖解析器中重新绑定默认IRepository值为 now DbRepository。有很多控制器吗?他们现在都将使用DbRepository. 这很酷。

阅读更多:

  1. 控制反转(维基)
  2. 依赖注入(维基)
  3. 控制容器的反转和依赖注入模式 (Martin Fowler)
于 2013-06-29T00:42:53.207 回答
41

Ninject 是一个控制反转容器。

它有什么作用?

假设您有一个Car依赖于类的Driver类。

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

为了使用Car您构建的类,如下所示:

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

IoC 容器集中了有关如何构建类的知识。它是一个中央存储库,知道一些事情。例如,它知道你需要用来建造汽车的具体类是 aDriver而不是任何其他的IDriver

例如,如果您正在开发 MVC 应用程序,您可以告诉 Ninject 如何构建您的控制器。您可以通过注册哪些具体类满足特定接口来做到这一点。在运行时,Ninject 将确定需要哪些类来构建所需的控制器,并且所有这些都在幕后。

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

这是有益的,因为它可以让您构建更容易进行单元测试的系统。假设Driver封装了所有数据库访问Car。在 Car 的单元测试中,您可以这样做:

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

有完整的框架可以自动为您创建测试类,它们被称为模拟框架。

了解更多信息:

于 2013-06-29T00:06:19.150 回答
7

其他答案很好,但我还想指出这篇使用 Ninject 实现依赖注入的文章。
这是我读过的最好的文章之一,它用一个非常优雅的例子解释了依赖注入和 Ninject。

这是文章的片段:

下面的接口将由我们的 (SMSService) 和 (MockSMSService) 实现,基本上新的接口 (ISMSService) 将公开两个服务的相同行为,如下面的代码:

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

(SMSService) 实现实现 (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)使用相同的接口具有完全不同的实现:

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

我们需要对我们的 (UIHandler) 类构造函数进行更改以通过它传递依赖关系,通过这样做,使用 (UIHandler) 的代码可以确定要使用 (ISMSService) 的哪个具体实现:

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

现在,我们必须创建一个继承自 (NinjectModule) 的单独类 (NinjectBindings)。此类将负责在运行时解决依赖关系,然后我们将覆盖用于在其中配置绑定的加载事件。Ninject 的好处是我们不需要更改 (ISMSService)、(SMSService) 和 (MockSMSService) 中的代码。

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

现在在 UI 表单代码中,我们将使用 Ninject 的绑定来确定要使用的实现:

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

现在代码使用 Ninject Kernal 来解决所有的依赖链,如果我们想在 Release 模式(在生产环境中)使用真实服务(SMSService)而不是 mock 服务,我们需要更改 Ninject 绑定类( NinjectBindings) 仅使用正确的实现或使用#if DEBUG 指令,如下所示:

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

}
 }

现在我们的绑定类(NinjectBindings)位于我们所有执行代码的顶部,我们可以在一个地方轻松地控制配置。


另请参阅什么是控制反转?提到了一些非常简单的例子来理解 IoC。

于 2017-02-21T18:57:23.190 回答
0

您必须首先了解依赖注入(DI)。注意这里,

public interface IService
{
    void Serve();
}
public class Service1 : IService
{
    public void Serve() {
        Console.WriteLine("Service1 Called");
    }
}
public class Service2 : IService
{
    public void Serve() {
        Console.WriteLine("Service2 Called");
    }
}
public class Service3 : IService
{
    public void Serve() {
        Console.WriteLine("Service3 Called");
    }
}
public class Client
{
    private IService service;

    public Client(IService _service)   //Constructor injection
    {
        service = _service;
    }
    public void ServeMethod() {
        service.Serve();  //Notice here, this Serve() method has no idea what to do.
    }                     // runtime will assign the object, that is Ninject
}


class Program
{
    static void Main(string[] args)
    {
        IService s1 = new Service1();  //N.B. Ninject assigns object with interface 

        Client c1 = new Client(s1);    

        c1.ServeMethod();         

        IService s2 = new Service2();  //N.B. Ninject assigns object with interface

        c1 = new Client(s2);

        c1.ServeMethod();

        IService s3 = new Service3(); //N.B. Ninject assigns object with interface

        c1 = new Client(s3);

        c1.ServeMethod();


        Console.ReadKey();
    }
}       
  // Ninject creates object in runtime for interface in runtime in ASP.NET MVC project.

/* 输出:
Service1 调用了 Service2
调用
了 Service3 调用了 */

于 2021-01-15T09:02:14.267 回答