我如何正确使用依赖注入?

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 成员的相关部分使用依赖项(你仍然没有提供),我的回答可能会有所不同。如果您提供更多上下文,它仍然可能会改变,但对于您要实现的目标,我只能做出这么多假设。填空会有很大帮助。