什么是Ninject,你什么时候使用它

本文关键字:什么时候 Ninject 什么 | 更新日期: 2023-09-27 18:20:32

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

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

什么是Ninject,你什么时候使用它

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>();。您可以根据需要快速切换 beetween 依赖项解析程序。看?很简单,你不需要写很多。

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

  3. 当你想用所有类中的其他东西替换DbRepository时,你必须做很多工作。

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

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

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

阅读更多:

  1. 控制反转(维基(
  2. 依赖注入(维基(
  3. 控制容器反转和依赖注入模式(Martin Fowler(

Ninject 是一个反转控制容器。

它有什么作用?

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

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

为了使用 Car 类,您可以像这样构建它:

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

IoC 包含程序集中了有关如何构建类的知识。它是一个中央存储库,知道一些事情。例如,它知道您需要用于制造汽车的具体类是Driver而不是任何其他IDriver

例如,如果你正在开发一个MVC应用程序,你可以告诉Ninject如何构建你的控制器。您可以通过注册哪些具体类满足特定接口来实现此目的。在运行时,Ninject 将确定构建所需控制器所需的类以及所有在幕后

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

这是有益的,因为它允许您构建更容易进行单元测试的系统。假设 Driver 封装了 Car 的所有数据库访问。在汽车的单元测试中,您可以执行以下操作:

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

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

欲了解更多信息:

  • GitHub/Ninject Home
  • 控制反转
  • 控制容器反转和依赖注入模式
  • 模拟对象

其他答案很好,但我也想指出这篇使用 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!");
 }
 }

现在,我们必须创建一个单独的类(NinjectBindings(,它继承自(NinjectModule(。此类将负责在运行时解析依赖项,然后我们将重写用于在其中配置绑定的 load 事件。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 内核来解决所有依赖链,如果我们想在发布模式(在生产环境中(而不是模拟模式下使用真正的服务 (SMSService(,我们需要更改 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。

您必须先了解依赖注入(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.

/*输出:
服务1 已调用
服务2 已调用
服务3 已调用*/