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

时间:2017-01-11 14:29:54

标签: c# dependency-injection unity-container

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

环境:Visual Studio 2015,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();

    }

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

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

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

1 个答案:

答案 0 :(得分:10)

依赖注入(DI)容器就是这样。促进DI的框架。您不能通过容器来解析对象的实例。您只需在类构造函数中请求所需类型,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 - &gt;依赖关系1 - &gt; Dependency2。

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

以下是重构代码:

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()
    {
    }
}

你现在注意到这些类现在都通过它们的构造函数来获取它们的依赖关系并且没有新的东西。类应该只接受它们实际需要的依赖项。例如,MyClass不需要Dependency2,所以它不会要求一个。它只询问Dependency1,因为它只需要它。 Dependency1 NEEDS Dependency2,而不是MyClass。

现在将它连接起来没有容器我们只需要在组合根中新建它:

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

如果我们有大量的课程和态度,你可以看到如何变得麻烦。这就是我们使用容器的原因。它为我们处理所有的depdency图。使用容器,我们将其重写如下:

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 {}

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

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

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

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

2)通过静态类请求您的容器:

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

            return container;
        }
    }
}

使用ServiceLocator类在合成根目录中正常注册所有内容。然后使用:

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