当某些参数仅在运行时已知时使用依赖注入的正确方法

Proper way to use dependency injection when some arguments will be known only at runtime

我在 Ninject wiki 上阅读了一堆问题和所有文档,但我仍然怀疑并向自己提出问题,例如“我做对了吗?”

  1. 如果我需要使用控制器来初始化对象怎么办?并且 field 参数仅在 runtime?
  2. 时才知道

A) 所以我需要这样的东西:

    public class MyClass : IMyInterface
    {
         private string _field;
         readonly IRepository _repo;
         public MyClass(string field, IRepository repo)
         {
             _field = field;
             _repo = repo;
         }
     }

但是如何正确绑定呢?或者除了构造函数依赖注入之外,我应该永远忘记使用构造函数吗?

B) 这样做:

public class MyClass : IMyInterface
{
    private string _field;
    readonly IRepository _repo;
    public MyClass(IRepository repo)
    {
        _repo = repo;
    }
    public void Initialize(string field)
    {
        _field = field;
    }
}

我认为当我需要对象时随时调用这个 Initialize 函数是不对的还是我错了?

根据这个回答Can't combine Factory / DI and Dependency Inject (DI) "friendly" library and Is there a pattern for initializing objects created via a DI container

Use Abstract Factory if you need a short-lived object

Dependencies injected with Constructor Injection tend to be long-lived, but sometimes you need a short-lived object, or to construct the dependency based on a value known only at run-time.

C) 我应该这样做:

public interface IMyInterface  {  }
  public interface IMyFactory
  {
      MyClass Create(string field);
  } 
  public class MyFactory : IMyFactory
  {
      private readonly IRepository _repo;
      public MyFactory (IRepository repo)
      {
          _repo = repo;
      }
      public MyClass Create(string field)
      {
          return new MyClass(_repo, field);
      }
  }
  public class MyClass : IMyInterface
  {
      private string _field;
      readonly IRepository _repo;
      public MyClass(IRepository repo, string field)
      {
          _repo = repo;
          _field = field;
      }
  }

如果我需要另一个 class 的 MyClass,我会像

那样使用它
public class OtherClass
{
    private IMyInterface _myClass;
    public OtherClass(IMyFactory factory)
    {
        _myClass = factory.Create("Something");
    }
}

是不是太笨重了?

而我的问题是:我必须使用 A、B 还是 C 大小写?为什么?还是别的?

What if I need to use controller for initialization of object? And field argument will be known only at runtime?

here 所述,您的应用程序组件在初始化期间不应需要运行时数据。相反,您应该:

  1. 通过 API 或
  2. 的方法调用传递运行时数据
  3. 从允许解析运行时数据的特定抽象中检索运行时数据。

与流行的看法相反,抽象工厂几乎不是这个问题的正确解决方案,因为:

Instead of lowering complexity for the consumer, a factory actually increases complexity, because instead of having just a dependency on the service abstraction IService, the consumer now requires a dependency on both IService and the Abstract Factory IServiceFactory. [...] the increase in complexity can be felt instantly when unit testing such classes. Not only does this force us to test the interaction the consumer has with the service, we have to test the interaction with the factory as well.

有关详细讨论,请阅读 this article

答案一如既往地视情况而定。没有上下文很难给出建议。

但是,有一件事我会犹豫不决 - 配置以将 "pure" 字符串注入组件。该字符串可能意味着成千上万的东西,以后可能很难支持该设计。当然,您可以定义注入到 XController 的字符串将是 SQL 的连接字符串,当注入到 YController 时将是授权令牌,但我怀疑它是否可读且易于维护。

据说我会选择选项 C 或以下方法:

    public class DescriptiveMeaningOfAClass
    {
         public string Value { get; set; }
     }

    public class MyClass : IMyInterface
    {
         private string _field;
         readonly IRepository _repo;
         public MyClass(DescriptiveMeaningOfAClass something, IRepository repo)
         {
             _field = something.Value;
             _repo = repo;
         }
     }

我个人会通过使用 ChildKernel 而不是直接引用构造函数来混合 A) 和 C)。 https://github.com/ninject/Ninject.Extensions.ChildKernel

在你的工厂:

var parentKernel = new StandardKernel();

var childKernel1 = new ChildKernel(parentKernel);
childKernel1.Bind<string>().ToConstant("MyRuntimeValue");
var myClass = childKernel1.Get<MyClass>();

我没有测试,但应该可以。