什么是Ninject,什么时候使用它?
我一直在帮助一些项目的朋友,还有一个使用Ninject的课程。 我对C#相当陌生,我不知道这个类正在做什么,这就是为什么我需要理解Ninject。 任何人都可以解释Ninject是什么以及什么时候使用它(如果可能,举例)? 或者,如果你可以指出一些很好的链接。
我试过这个问题: Ninject教程/文档? 但对于像我这样的初学者并没有什么帮助。
Ninject是.NET的dependency injection器,模式dependency injection的实际实现(控制模式的反转forms)。
假设你有两个类DbRepository
和Controller
:
class Controller { private DbRepository _repository; // ... some methods that uses _repository } class DbRepository { // ... some bussiness logic here ... }
所以,现在你有两个问题:
-
您必须无偿使用_repository。 你有这样的方式:
- 在构造函数中手动。 但是,如果DbRepository的构造函数改变呢? 您需要重写您的
Controller
类,因为代码的其他部分已更改。 如果你只有一个Controller
,这并不难,但是如果你有一些依赖于你的Repository
的类,你就会遇到一个问题。 -
您可以使用服务定位器或工厂或smth。 现在你依赖于你的服务定位器。 你有一个全球服务定位器,所有的代码必须使用它。 当你需要在代码的一部分中使用一个激活逻辑和其他代码中的其他部分时,你将如何改变服务定位器的行为? 只有一个办法 – 通过构造函数传递服务定位器。 但随着越来越多的课程,你将需要越来越多地通过它。 无论如何,这是个好主意,但这是个坏主意。
class Controller { private DbRepository _repository; public Controller() { _repository = GlobalServiceLocator.Get<DbRepository>() } // ... some methods that uses _repository }
-
您可以使用dependency injection。 看代码:
class Controller { private IRepository _repository; public Controller(IRepository repository) { _repository = repository; } }
现在,当你需要你的控制器,你写:
ninjectDevKernel.Get<Controller>();
或者ninjectTestKernel.Get<Controller>();
。 您可以尽可能快地切换依赖关系parsing器。 看到? 这很简单,你不需要写很多东西。 - 在构造函数中手动。 但是,如果DbRepository的构造函数改变呢? 您需要重写您的
-
你不能对它进行unit testing。 你的
Controller
依赖于DbRepository
,如果你想testing一些使用仓库的方法,你的代码会去数据库并要求它提供数据。 这很慢,很慢。 如果您在DbRepository
的代码发生更改,您的Controller
上的unit testing将会落空。 在这种情况下,只有集成testing必须说“问题”。 你在unit testing中需要什么 – 隔离你的类并在一个testing中只testing一个类(在理想情况下 – 只有一种方法)。 如果你的DbRepository
代码失败了,你会认为Controller
代码失败 – 这很糟糕(即使你有DbRepository
和Controller
testing – 它们都会失败,你可以从错误的地方开始)。 确定错误的位置需要很长时间。 你需要知道某个class级是好的,而其他class级则是失败的。 -
当你想用所有类中的其他东西替代
DbRepository
时,你必须做很多工作。 -
你不能轻易控制
DbRepository
生命周期。 该类的对象在Controller
创build上创build并在Controller
删除时删除。Controller
类的不同实例之间没有共享,其他类没有共享。 与Ninject,你可以简单地写:kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
dependency injection的特性 – 敏捷开发! 您描述了您的控制器使用接口IRepository
存储库。 您不需要编写DbRepository
,您可以编写简单的MemoryRepository
类并开发Controller
而其他人则开发DbRepository
。 当DbRepository
完成时,您只需在您的依赖关系parsing器中DbRepository
默认的IRepository
现在是DbRepository
。 你写了很多控制器? 他们都将使用现在的DbRepository
。 这很酷。
阅读更多:
- 控制反转(wiki)
- dependency injection(wiki)
- 控制容器的倒置和dependency injection模式(Martin Fowler)
Ninject是一个控制容器的反转。
它有什么作用?
假设你有一个Car
类,它依赖于一个Driver
类。
public class Car { public Car(IDriver driver) { /// } }
为了使用Car
类,您可以这样构build它:
IDriver driver = new Driver(); var car = new Car(driver);
IoC遏制者集中了关于如何构build类的知识。 这是一个知道一些事情的中央知识库。 例如,它知道你需要用来build造一辆汽车的具体类是一个Driver
而不是任何其他的IDriver
。
例如,如果你正在开发一个MVC应用程序,你可以告诉Ninject如何构build你的控制器。 您可以通过注册哪些具体的类满足特定的接口。 运行时,Ninject会计算出需要哪些类来构build所需的控制器,以及所有后台的情况。
// Syntax for binding Bind<IDriver>().To<Driver>();
这是有益的,因为它可以让你构build更容易unit testing的系统。 假设Driver
封装了Car
所有数据库访问。 在Car的unit testing中,你可以这样做:
IDriver driver = new TestDriver(); // a fake driver that does not go to the db var car = new Car(driver);
有整个框架,照顾自动创buildtesting类,他们被称为嘲笑框架。
了解更多信息:
- GitHub / Ninject Home
- 控制反转
- 控制容器的反转和dependency injection模式
- 模拟对象
其他答案很好,但我也想指出这个实现dependency injection使用Ninject文章。
这是我阅读过的最好的文章之一,它以一个非常优雅的例子解释了dependency injection和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!"); } }
现在,我们必须创build一个从(NinjectModule)inheritance的独立类(NinjectBindings)。 这个类将负责在运行时parsing依赖关系,然后我们将覆盖用于在其中configuration绑定的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)是所有我们的执行代码的顶部,我们可以一次轻松地控制configuration。
另请参阅什么是控制反转? 提到一些非常简单的例子来理解IoC。