我如何正确使用依赖注入?
How do i use dependency injection correct?
所以,这是我第一次使用 Ninject,一切都很好,直到我 运行 遇到了一些服务 类 需要在构造函数中添加 ModelState 参数的问题。通过回答这个问题解决了问题:
现在下一个问题是我有大约 40 个服务 类 需要在构造函数中访问 ModelState,因此我选择使用泛型,我真的不知道这是否真的合法反正?还是我必须重做?
private readonly IServiceFactory<IAccountService, AccountService> service;
public AccountController(IServiceFactory<IAccountService, AccountService> asf)
{
this.service = asf;
}
来自 Ninject 的注册服务:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind(typeof(IServiceFactory<,>)).To(typeof(ServiceFactory<,>));
}
泛型:
public interface IServiceFactory<T, Y> where T : class where Y : class
{
T Create(ModelStateDictionary modelState);
}
public class ServiceFactory<T, Y> : IServiceFactory<T, Y>
where T : class
where Y : class
{
public T Create(ModelStateDictionary modelState)
{
var x = (T) Activator.CreateInstance(typeof (Y), new ModelStateWrapper(modelState));
return x;
}
}
我试图实现的是,我想避免创建 40 个工厂接口和 40 个工厂来制作完全相同的东西。我的解决方案有效,但它真的支持依赖注入吗?我真的想不通。
依赖注入将具体实现映射到abstract/interface并将该实例传递给依赖class.
映射:
kernel.Bind<IServiceAccount>().To<ServiceAccount>();
相关 class:
private readonly IServiceAccount _serviceAccount;
public AccountController(IServiceAccount serviceAccount)
{
_serviceAccount = serviceAccount;
}
在你的情况下,你似乎应该重新考虑你的服务的工作方式,除非你不想为你使用的每个服务创建一个工厂,你需要构造函数上的模型状态吗?也许你可以将它更改为在您使用服务时在您的服务中使用它的方法,如果您的工作流不允许无效状态抛出异常。
如果您需要使用运行时值来创建服务实例,则需要为其创建一个工厂。
public class ValidServiceAccount : IServiceAccount
{}
public class InvalidServiceAccount : IServiceAccount
{}
public interface IServiceAccountFactory
{
IServiceAccount Create(bool modelState);
}
public class ServiceAccountFactory : IServiceAccountFactory
{
public IServiceAccount Create(bool modelState)
{
return modelState ? new ValidServiceAccount() : new InvalidServiceAccount();
}
}
而不是在 yoru 控制器构造函数上传递一个 IServiceAccount,而是传递一个 IServiceAccountFactory,您可以看到这很容易给您的项目增加很多复杂性,但是当您真正需要灵活性时,它确实会带来回报。
注意:在 asp.net MVC 项目中,您应该获得 ninject 控制器工厂,以便它为您处理构造函数(我认为该包名为 Ninject.MVC)。
如果你注意到的最后一部分,你也可以直接通过IAccountService
接口传递对ModelState的引用。在这种情况下,这会简化事情,但鉴于您之前的问题缺乏上下文,不清楚哪个是更好的选择。
public class AccountController : Controller
{
private readonly ILanguageService languageService;
private readonly ISessionHelper sessionHelper;
private readonly IAccountService accountService;
public AccountController(ILanguageService languageService, ISessionHelper sessionHelper, IAccountService accountService)
{
if (languageService == null)
throw new ArgumentNullException("languageService");
if (sessionHelper == null)
throw new ArgumentNullException("sessionHelper");
if (accountService == null)
throw new ArgumentNullException("accountService");
this.languageService = languageService;
this.sessionHelper = sessionHelper;
this.accountService = sessionHelper;
}
[HttpPost]
public ActionResult PostSomething()
{
// Pass model state of the current request to the service.
this.accountService.DoSomething(new ModelStateWrapper(this.ModelState));
}
}
这显然比注入 40 个工厂更容易,并且会使您的 DI 配置更简单。
这种方法的唯一缺点(如果您可以这样称呼的话)是您的 IAccountService
接口现在需要传递 IValidationDictionary
的实现。这意味着 IAccountService
的每个具体实现都必须依赖于 ModelState 的抽象 IValidationDictionary
。如果这是设计的期望,那很好。
此外,您应该在 class 中放置保护子句,以确保如果 DI 容器无法提供实例,则会抛出异常。私有字段中的 readonly
关键字使得无法在构造函数之外更改这些字段的值。基本上,这两件事结合起来保证您将拥有一个在构造函数中初始化的实例,并且在对象的整个生命周期中保持不变。
最后的想法
DI 本质上是复杂的。如果你想得到一个好的答案,你应该提供更多关于这个问题的背景信息。我意识到 SO 说你应该 post 所需的最少代码,但是由于 DI 的复杂性,与其他问题相比,关于 DI 的问题通常需要更多的上下文来回答。
如果我知道你正在尝试解决 40 个类似服务的问题,或者知道 IAccountService
成员的相关部分使用依赖项(你仍然没有提供),我的回答可能会有所不同。如果您提供更多上下文,它仍然可能会改变,但对于您要实现的目标,我只能做出这么多假设。填空会有很大帮助。
所以,这是我第一次使用 Ninject,一切都很好,直到我 运行 遇到了一些服务 类 需要在构造函数中添加 ModelState 参数的问题。通过回答这个问题解决了问题:
现在下一个问题是我有大约 40 个服务 类 需要在构造函数中访问 ModelState,因此我选择使用泛型,我真的不知道这是否真的合法反正?还是我必须重做?
private readonly IServiceFactory<IAccountService, AccountService> service;
public AccountController(IServiceFactory<IAccountService, AccountService> asf)
{
this.service = asf;
}
来自 Ninject 的注册服务:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind(typeof(IServiceFactory<,>)).To(typeof(ServiceFactory<,>));
}
泛型:
public interface IServiceFactory<T, Y> where T : class where Y : class
{
T Create(ModelStateDictionary modelState);
}
public class ServiceFactory<T, Y> : IServiceFactory<T, Y>
where T : class
where Y : class
{
public T Create(ModelStateDictionary modelState)
{
var x = (T) Activator.CreateInstance(typeof (Y), new ModelStateWrapper(modelState));
return x;
}
}
我试图实现的是,我想避免创建 40 个工厂接口和 40 个工厂来制作完全相同的东西。我的解决方案有效,但它真的支持依赖注入吗?我真的想不通。
依赖注入将具体实现映射到abstract/interface并将该实例传递给依赖class.
映射:
kernel.Bind<IServiceAccount>().To<ServiceAccount>();
相关 class:
private readonly IServiceAccount _serviceAccount;
public AccountController(IServiceAccount serviceAccount)
{
_serviceAccount = serviceAccount;
}
在你的情况下,你似乎应该重新考虑你的服务的工作方式,除非你不想为你使用的每个服务创建一个工厂,你需要构造函数上的模型状态吗?也许你可以将它更改为在您使用服务时在您的服务中使用它的方法,如果您的工作流不允许无效状态抛出异常。
如果您需要使用运行时值来创建服务实例,则需要为其创建一个工厂。
public class ValidServiceAccount : IServiceAccount
{}
public class InvalidServiceAccount : IServiceAccount
{}
public interface IServiceAccountFactory
{
IServiceAccount Create(bool modelState);
}
public class ServiceAccountFactory : IServiceAccountFactory
{
public IServiceAccount Create(bool modelState)
{
return modelState ? new ValidServiceAccount() : new InvalidServiceAccount();
}
}
而不是在 yoru 控制器构造函数上传递一个 IServiceAccount,而是传递一个 IServiceAccountFactory,您可以看到这很容易给您的项目增加很多复杂性,但是当您真正需要灵活性时,它确实会带来回报。
注意:在 asp.net MVC 项目中,您应该获得 ninject 控制器工厂,以便它为您处理构造函数(我认为该包名为 Ninject.MVC)。
如果你注意到IAccountService
接口传递对ModelState的引用。在这种情况下,这会简化事情,但鉴于您之前的问题缺乏上下文,不清楚哪个是更好的选择。
public class AccountController : Controller
{
private readonly ILanguageService languageService;
private readonly ISessionHelper sessionHelper;
private readonly IAccountService accountService;
public AccountController(ILanguageService languageService, ISessionHelper sessionHelper, IAccountService accountService)
{
if (languageService == null)
throw new ArgumentNullException("languageService");
if (sessionHelper == null)
throw new ArgumentNullException("sessionHelper");
if (accountService == null)
throw new ArgumentNullException("accountService");
this.languageService = languageService;
this.sessionHelper = sessionHelper;
this.accountService = sessionHelper;
}
[HttpPost]
public ActionResult PostSomething()
{
// Pass model state of the current request to the service.
this.accountService.DoSomething(new ModelStateWrapper(this.ModelState));
}
}
这显然比注入 40 个工厂更容易,并且会使您的 DI 配置更简单。
这种方法的唯一缺点(如果您可以这样称呼的话)是您的 IAccountService
接口现在需要传递 IValidationDictionary
的实现。这意味着 IAccountService
的每个具体实现都必须依赖于 ModelState 的抽象 IValidationDictionary
。如果这是设计的期望,那很好。
此外,您应该在 class 中放置保护子句,以确保如果 DI 容器无法提供实例,则会抛出异常。私有字段中的 readonly
关键字使得无法在构造函数之外更改这些字段的值。基本上,这两件事结合起来保证您将拥有一个在构造函数中初始化的实例,并且在对象的整个生命周期中保持不变。
最后的想法
DI 本质上是复杂的。如果你想得到一个好的答案,你应该提供更多关于这个问题的背景信息。我意识到 SO 说你应该 post 所需的最少代码,但是由于 DI 的复杂性,与其他问题相比,关于 DI 的问题通常需要更多的上下文来回答。
如果我知道你正在尝试解决 40 个类似服务的问题,或者知道 IAccountService
成员的相关部分使用依赖项(你仍然没有提供),我的回答可能会有所不同。如果您提供更多上下文,它仍然可能会改变,但对于您要实现的目标,我只能做出这么多假设。填空会有很大帮助。