我一直在帮助一些项目的朋友,并且有一个使用Ninject的课程。我是C#的新手,我不知道那个班级在做什么,这就是我需要了解Ninject的原因。任何人都可以解释Ninject是什么以及何时使用它(如果可能,请举例说明)?或者,如果你可以指出一些很棒的链接。
我尝试过这个问题:Ninject tutorials/documentations?但它并没有真正帮助像我这样的初学者。
答案 0 :(得分:46)
Ninject是.NET的依赖注入器,模式依赖注入的实际实现(控制模式的反转形式)。
假设您有两个班级DbRepository
和Controller
:
class Controller {
private DbRepository _repository;
// ... some methods that uses _repository
}
class DbRepository {
// ... some bussiness logic here ...
}
所以,现在你有两个问题:
您必须小心_repository
才能使用它。你有这样的方式:
Controller
类,因为其他部分代码已更改。如果您只有一个Controller
并不难,但如果您有几个依赖于Repository
的课程,则会遇到真正的问题。 您可以使用服务定位器或工厂或smth。现在您依赖于服务定位器。您有一个全局服务定位器,所有代码都必须使用它。当您需要在代码的一部分中使用一个激活逻辑以及在代码的其他部分中使用其他部分时,您将如何更改服务定位器的行为?只有一种方法 - 通过构造函数传递服务定位器。但随着越来越多的课程,你需要越来越多地通过它。无论如何,这是个好主意,但这是个坏主意。
class Controller {
private DbRepository _repository;
public Controller() {
_repository = GlobalServiceLocator.Get<DbRepository>()
}
// ... some methods that uses _repository
}
您可以使用依赖注入。看一下代码:
class Controller {
private IRepository _repository;
public Controller(IRepository repository) {
_repository = repository;
}
}
现在,当您需要控制器时,请写下:ninjectDevKernel.Get<Controller>();
或ninjectTestKernel.Get<Controller>();
。您可以根据需要快速切换依赖项解析器之间的beet。看到?这很简单,你不需要写很多东西。
您无法对其进行单元测试。您的Controller
依赖DbRepository
,如果您想测试一些使用存储库的方法,您的代码将转到数据库并询问数据。那很慢,很慢。如果DbRepository
中的代码发生变化,则Controller
上的单元测试将会下降。在这种情况下,只有集成测试必须说“问题”。您在单元测试中需要什么 - 隔离您的类并在一个测试中仅测试一个类(理想情况下 - 只有一个方法)。如果您的DbRepository
代码失败,您会认为Controller
代码失败了 - 这很糟糕(即使您对DbRepository
和Controller
进行了测试 - 它们都会失败并且您可以从错误的地方开始)。确定错误的位置需要花费大量时间。你需要知道某些类是正常的,而其他一些类是失败的。
当您想要在所有课程中使用其他内容替换DbRepository
时,您必须做很多工作。
您无法轻松控制DbRepository
的生命周期。此类的对象在Controller
的初始化时创建,并在Controller
删除时删除。 Controller
类的不同实例之间没有共享,并且在其他类之间没有共享。使用Ninject,您可以简单地写:
kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
依赖注入的特殊功能 - 敏捷开发!您描述了控制器使用带有接口IRepository
的存储库。你不需要写DbRepository
,你可以编写简单的MemoryRepository
类并开发Controller
,而其他人开发DbRepository
。完成DbRepository
后,您只需在您的依赖项解析程序中重新绑定,现在默认IRepository
为DbRepository
。你有很多控制器吗?所有这些人现在都会使用DbRepository
。这很酷。
了解更多:
答案 1 :(得分:41)
Ninject是一个控制反转容器。
它做了什么?
假设您有一个取决于Car
类的Driver
类。
public class Car
{
public Car(IDriver driver)
{
///
}
}
为了使用Car
类,你可以这样构建它:
IDriver driver = new Driver();
var car = new Car(driver);
IoC containter集中了有关如何构建类的知识。它是一个知道一些事情的中央存储库。例如,它知道您需要用于构建汽车的具体类是Driver
而不是任何其他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);
有整个框架负责为您自动创建测试类,它们被称为模拟框架。
了解更多信息:
答案 2 :(得分:6)
其他答案很棒,但我也想指出这篇Implementing Dependency Injection using 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!");
}
}
现在,我们必须创建一个继承自(NinjectModule)的独立类(NinjectBindings)。此类将负责在运行时解析依赖项,然后我们将覆盖用于在其中配置绑定的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 Kernal来解析所有依赖链,如果我们想要在Release模式下使用真实服务(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)位于我们所有执行代码的顶部,我们可以在一个位置轻松控制配置。
另外,请参阅What is Inversion of Control?一些非常简单的例子来理解IoC。