什么是Ninject,你什么时候使用它
本文关键字:什么时候 Ninject 什么 | 更新日期: 2023-09-27 18:20:32
我一直在帮助几个朋友做一个项目,有一个使用Ninject的类。我对 C# 相当陌生,我不知道那个类在做什么,这就是为什么我需要了解 Ninject。谁能解释一下Ninject是什么以及何时使用它(如果可能的话,举例(?或者,如果您可以指出一些链接,那也很棒。
我试过这个问题:Ninject教程/文档?但它并没有真正帮助像我这样的初学者。
Ninject是.NET的依赖注入器,是模式依赖注入(控制模式反转的形式(的实际实现。
假设您有两个类DbRepository
和Controller
:
class Controller {
private DbRepository _repository;
// ... some methods that uses _repository
}
class DbRepository {
// ... some bussiness logic here ...
}
所以,现在你有两个问题:
您必须初始化
_repository
才能使用它。您可以通过多种方式执行此操作:- 手动,在构造函数中。但是,如果 DbRepository 的构造函数发生变化怎么办?您需要重写
Controller
类,因为它所依赖的代码已更改。如果你只有一个Controller
并不难,但是如果你有几个类依赖于你的Repository
你就有一个真正的问题。 您可以使用服务定位器或工厂。但现在您依赖于服务定位器。您有一个全局服务定位器,所有代码都必须使用它。当您需要在代码的一部分中将其用于激活逻辑,而在代码的另一部分中用于其他内容时,您将如何更改服务定位器的行为?只有一种方法 - 通过构造函数传递服务定位器。但是随着越来越多的课程,您将需要通过越来越多的次数。无论如何,这是一个好主意,但从长远来看,这是一个坏主意。
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>();
。您可以根据需要快速切换 beetween 依赖项解析程序。看?很简单,你不需要写很多。
- 手动,在构造函数中。但是,如果 DbRepository 的构造函数发生变化怎么办?您需要重写
不能为其创建单元测试。您的
Controller
依赖于DbRepository
,如果您想测试某些使用存储库的方法,您的代码将转到数据库并向其请求数据。这很慢,非常慢。如果DbRepository
中的代码发生更改,则Controller
上的单元测试将下降。在这种情况下,只有集成测试必须警告您"问题"。在单元测试中需要的是隔离您的类并在一个测试中只测试一个类(理想情况下 - 只有一个方法(。如果你的DbRepository
代码失败了,你会认为Controller
代码失败了——这很糟糕(即使你对DbRepository
和Controller
进行了测试——它们都会失败,你可以从错误的地方开始(。确定错误的真正位置需要花费大量时间。你需要知道A类是可以的,而且是B类失败了。当你想用所有类中的其他东西替换
DbRepository
时,你必须做很多工作。您无法轻松控制
DbRepository
的生命周期。此类的对象在初始化Controller
时创建,并在删除Controller
时删除。Controller
类的不同实例之间没有共享,其他类之间也没有共享。使用 Ninject,您可以简单地编写:kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
依赖注入的一个特点——敏捷开发!您描述您的控制器使用带有接口 IRepository
的存储库。你不需要写DbRepository
,你可以简单地创建一个MemoryRepository
类并开发Controller
而另一个人开发DbRepository
。完成DbRepository
的工作后,只需在依赖项解析器中重新绑定默认IRepository
现在DbRepository
。有很多控制器?他们现在都将使用 DbRepository
.很酷。
阅读更多:
- 控制反转(维基(
- 依赖注入(维基(
- 控制容器反转和依赖注入模式(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 已调用*/