依赖注入容器 - 如何保持可用

Dependency Injection Container - How to keep available to

在使用依赖注入创建应用程序时,它利用依赖注入框架,例如 Unity(或 Ninject)。
您如何在一开始就初始化将接口注册到容器,并使它们可供应用程序在其 运行 应用程序的整个生命周期中使用?
您是否需要将 DI 容器传递给可能利用依赖注入的每个方法,或者是否有某种方法可以使容器全局可访问,以便您可以在开始时将它们一起注册并在整个 运行 应用程序中访问它们不必不断地通过它们,并且能够在需要时使用它们?

环境:Visual Studio2015、C#、Microsoft Unity(用于 DI 容器)

示例代码

    static void Main(string[] args)
    {

        // Make Unity resolve the interface, providing an instance
        // of TrivialPursuit class
        var diContainer = new UnityContainer();
        diContainer.RegisterType<IGame, TrivialPursuit>();

        var gameInstance = diContainer.Resolve<IGame>();


        var xotherClass = new AnotherClass();
        xotherClass.TestOtherClassOtherMethod();

    }

------ 另一个 class 没有依赖注入的上下文 Class ------

    public void TestOtherClassOtherMethod()
    {
        IGame gameInstance = -- -Container is Not available to resolve from in this class ---
    }

原因:我不想将我以后可能需要的所有可能类型传递给我加载的每个 class,我只想在需要时使用这些实例。我对 classes 的研究越深入,后来随着应用程序变得越来越复杂,我不想将每种类型的实例从 Main() 方法向上传递给每个 class.

依赖注入 (DI) 容器就是这样。促进 DI 的框架。您不会为了解析对象的实例而四处传递容器。您只需在 classes 构造函数中请求所需的类型,DI 框架就会注入适当的依赖项。

Mark Seemann 写了一篇很好的文章 book on dependency injection 我会推荐。

您在组合根目录中注册所有需要用容器解析的内容。也就是说你的程序启动的时候就是应该注册的时候。

假设我们有以下代码:

public class MyClass
{
    public Run()
    {
        var dependency = new Dependency1();
        dependency.DoSomething();
    }
}

public class Dependency1
{
    public void DoSomething()
    {
        var dependency = new Dependency2();
        dependeny.DoSomethingElse();
    }
}

public class Dependency2
{
    public void DoSomethingElse()
    {
    }
}

这给了我们上面的依赖链:MyClass -> Dependency1 -> Dependency2。

我们应该做的第一件事是重构 classes 以通过它们的构造函数获取它们的依赖关系,并依赖于接口而不是实体。我们无法注入依赖项,除非有地方可以注入它们(构造函数、属性 等)。

这是重构后的代码:

public interface IMyClass
{
    void Run();
}

public interface IDependency1
{
    void DoSomething();
}

public interface IDependency2
{
    void DoSomethingElse();
}

public class MyClass : IMyClass
{
    public readonly IDependency1 dep;

    public MyClass(IDependency1 dep)
    {
        this.dep = dep;
    }

    public void Run()
    {
        this.dep.DoSomething();
    }
}

public class Dependency1 : IDependency1
{
    public readonly IDependency2 dep;

    public MyClass(IDependency2 dep)
    {
        this.dep = dep;
    }

    public void DoSomething()
    {
        this.dep.DoSomethingElse();
    }
}

public class Dependency2 : IDependency2
{
    public void DoSomethingElse()
    {
    }
}

您会注意到 classes 现在都通过它们的构造函数获取它们的依赖关系,而不是 new 任何东西。 类 应该只接受他们实际需要的依赖项。例如,MyClass 不需要 Dependency2,因此它不会要求一个。它只要求 Dependency1,因为这就是它所需要的。 Dependency1 需要 Dependency2,而不是 MyClass。

现在要在没有容器的情况下将其全部连接起来,我们只需在组合根中将其全部新建即可:

void Main()
{
    var myClass = new MyClass(new Dependency1(new Dependency2()));
}

你可以看到如果我们有大量的 classes 和依赖项,那会变得多么麻烦。这就是我们使用容器的原因。它为我们处理所有的依赖图。使用容器,我们将其重写如下:

void Main()
{
    // the order of our registration does not matter.
    var container = new Container();
    container.Register<IDependency1>.For<Dependency1>();
    container.Register<IDependency2>.For<Dependency2>();
    container.Register<IMyClass>.For<MyClass>();

    // then we request our first object like in the first example (MyClass);
    var myClass = container.Resolve<IMyClass>();

    myClass.Run();
}

在第二个示例中,容器将处理连接所有依赖项。所以我们永远不需要将 Depedency2 传递给 MyClass,然后再传递给 Depedency1。我们只需要在 Dependency1 中请求它,容器就会像第一个例子一样为我们连接它。

所以在你的例子中我们会这样重写它:

static void Main(string[] args)
{
    var game = new UnityContainer();
    game.RegisterType<IGame, TrivialPursuit>();
    game.RegisterType<IAnotherClass, AnotherClass>();
    game.RegisterType<IYetAnotherClass, YetAnotherClass>();

    var gameInstance = game.Resolve<IGame>();
    // you'll need to perform some action on gameInstance now, like gameInstance.RunGame() or whatever.
}

public class Game : IGame
{
    public Game(IAnotherClass anotherClass)
    {
    }
}    

public class AnotherClass : IAnotherClass
{
    public AnotherClass(IYetAnotherClass yetAnotherClass)
    {
    }
}

public class YetAnotherClass : IYetAnotherClass {}

在这些情况下,无需传递容器。您向容器注册依赖项,然后在 classes 构造函数中请求它们。如果您希望在 class 中使用容器而不通过构造函数请求它,那么您不是在进行 DI,您只是将容器用作单例服务定位器。通常应该避免的事情。

容器即服务定位器 通常应该避免这种情况,但如果您想将容器用作服务定位器,您有两个选择:

1) 通过构造函数将容器传递给需要它的 classes。 您可以使用上述示例为 DI 连接 classes。但不是在构造函数中请求像 IDependency 这样的依赖项,而是传递容器。

public class Game : IGame
{
    public Game(IContainer container)
    {
        var blah = container.Resolve<IBlah>();
    }
}

2) 通过静态 class:

请求容器
public static class ServiceLocator
{
    private static IContainer container;
    public static IContainer Container
    {
        get 
        {
            if (container == null)
            {
                container = new Container();
            }

            return container;
        }
    }
}

使用 ServiceLocator class 在组合根中正常注册所有内容。然后使用:

public class MyClass
{
    public void DoSomething()
    {
        var blah = ServiceLocator.Container.Resolve<IBlah>();
    }
}