使用new运算符创建对象/依赖项与使用DI容器

时间:2014-05-14 10:56:11

标签: c# design-patterns dependency-injection inversion-of-control ioc-container

通过构造函数注入手动连接依赖关系有什么不好,以及使用DI容器(container.Resolve<T>())的那么好?

我的意思是,除了每次需要创建依赖对象并为其提供所有依赖项时都必须再键入一些键击的事实。

事实上,拥有一个像DI Container这样的中央注册表可能会让您感到更加困惑,并且会从您手中夺走控制权。

我已经有了这个问题和另一个(下面)有一段时间,但我猜Mark Seemann的文章Service Locator is an anti-pattern让我最终在这里输入这个问题。

所以,我的另一个问题是:

如果某个依赖项本身需要一些动态(比如说用户提供的)输入,那该怎么办?比如说:

class Dependency : IDependency
{
  public Dependency(string userInput) { }
}

class Dependent
{
    IDependency _dependency;

    void DoSomething()
    {
      var container = new MyFavoriteContainerFromTheMarket();

      Console.Write("Hey, user. How are you feeling?");
      var userInput = Console.ReadLine();

      // Do DI containers have overloads for Resolve instead of for Register
      // that accept parameters to be passed to constructors?
      // In other words, what if I don't know the constructor parameter
      // value at the time of registration of the dependency but only
      // get to know it just before I have to resolve/instantiate the
      // dependency? Do popular DI containers of today have overloads
      // for that? I assume they must
      _dependency = container.Resolve<IDependency>(userInput);
    }
}

总的来说,感觉DI容器没有从你手中夺走控制权吗?在某些情况下手动连接依赖项是不是没有问题?

当然,我理解有时它很简单并且可以节省您的输入并创建一个更干净,更简洁的代码片段,但让它们混淆了 - 一些依赖项由一个容器管理而另一些依赖于您自己手动供应 - 所有这些都让它如此混乱并迫使你记住哪一个是哪个,从而使维护更加困难,你不同意吗?

更新

哦,等等。我刚刚意识到我的整个例子令人费解。我原本打算从main()中的客户端代码调用Resolve。我最初应该用这个问题发布的代码应该是这样的:

class Dependency : IDependency
{
  public Dependency(string userInput) { }
}

class Dependent
{
    IDependency _dependency;

    public Dependent(IDependency dependency)
    {
      _dependency = dependency;
    }

    public void DoSomething()
    {
    }
}

class Program
{
  public static void Main(string[] args)
  {
      var container = new MyFavoriteContainerFromTheMarket();

      container.Register<IDependency>(new Dependency(/* I want this to come later */));

      container.Register<Dependent>(new Dependent(), 
       new ConstructorInjection<IDependency>());

      Console.Write("Hey, user. How are you feeling?");
      var userInput = Console.ReadLine();

      // It appears I can't do this as it makes perfect sense
      // to have the whole tree of dependencies known at the time
      // of registration.
      var dependent = container.Resolve<Dependent>(userInput);

      dependent.DoSomething();
  }
}

我不知道该更新对之前讨论的影响。看起来好像有些白痴(我)在询问之前没有脑子里的问题。提出问题本身就迫使正确的答案,@ Maarten确认并试图帮助我。

抱歉,@ Maarten。我浪费了你的时间。 : - )

1 个答案:

答案 0 :(得分:4)

您不应该尝试通过调用容器来解析实例。您应该使用构造函数注入来注入依赖项。 Mark Seeman将此称为Don't call the container, let it call you

要解决使用运行时值创建注入类型实例的问题,请使用abstract factories

所以你的例子可以这样解决:

class Dependency : IDependency
{
  public Dependency(string userInput) { }
}

interface IDependencyFactory {
    IDependency Create(string userInput);
}

class DependencyFactory: IDependencyFactory {
    IDependency Create(string userInput) {
        return new Dependency(userInput);
    }
}

class Dependent {
    public Dependent(IDependencyFactory factory) {
        // guard clause omitted
        _factory = factory
    }

    private readonly IDependencyFactory _factory;

    void DoSomething() {
        // Do not call the container, let it call you.
        // So no container usage here.
        //var container = ...

        Console.Write("Hey, user. How are you feeling?");
        var userInput = Console.ReadLine();

        var dependency = _factory.Create(userInput);
        // There you go!
    }
}