【问题标题】:What is a good strategy for initializing an IoC container within a class library?在类库中初始化 IoC 容器的好策略是什么?
【发布时间】:2014-07-17 20:13:57
【问题描述】:

我正在编写一个类库(在 C# 中),它将与我无法控制的应用程序一起分发。我的库也有点安全敏感,因此我不想允许调用应用程序对类库的依赖项进行任何配置。它必须是自包含的并初始化自己的依赖项。

同时,我希望它是可单元测试的和松耦合的,并且我想使用 IoC 容器来管理依赖项。目前我正在使用内部构造函数和[InternalsVisibleTo()],以便单元测试可以进行手动注入。我更喜欢使用 Ninject 作为我的 IoC 容器,但我认为这与问题无关。

我正在努力想出一个在生产中初始化我的 IoC 容器的好策略,因为类库实际上并没有定义 入口点,它有许多类可以在不知道应用首先使用哪个的情况下进行实例化。

我想知道是否可能存在某种AssemblyLoad 事件,并且确实AppDomain 似乎有这样的事件,但我的程序集必须已经加载到 AppDomain 中,然后我才能连接到它,所以我总是会错过由我自己的程序集加载引发的事件。我还考虑过使用静态初始化程序或构造函数,但我不太乐意使用 IoC 容器设置来污染每个可能的类,因为这会使它们与容器紧密耦合。我不想只解耦我的代码,然后将其耦合到 IoC 容器。

我发现了一些其他问题讨论这个话题,但没有一个真正涉及我的情况。一个建议使用静态初始化器,另一个建议应用程序应该始终是组合根。

还有其他方法吗?

【问题讨论】:

  • 您的消费者会使用 Ninject 来获取您的实现吗?如果没有,你打算如何使用 Ninject 将任何东西注入你的类?
  • 我的消费者可能会也可能不会使用 Ninject,我不知道。我的类库可能会作为 NuGet 包使用,因此我将在该级别依赖 Ninject。

标签: c# ninject inversion-of-control class-library


【解决方案1】:

您的要求之间存在矛盾。

首先,出于安全考虑,您不需要合成根。其次,您希望容器解决依赖关系。但是由于库不控制容器,几乎任何东西都可以注入到你的代码中,包括试图从内部破坏任何东西的东西。

一种方法是让您的依赖项显式化,以便库客户端负责提供您的依赖项。

namespace Library
{
    public class Foo1
    {
        //  a classical IoC dependendency
        public Foo1( IBar bar )
        {
        }
    }
}

另一方面,使用作为初始化库的显式外部点的组合根仍然不意味着您的库已被污染。 CR 与负责内部对象创建的本地工厂(又名依赖解析器)配合得很好,它是在 CR 中设置的。

namespace Library
{
    public interface IFoo { }

    // local Foo factory, with a customizable provider
    public class FooFactory
    {
        private static Func<IFoo> _provider;
        public static void SetProvider( Func<IFoo> provider )
        {
           _provider = provider;
        }

        public IFoo CreateFoo()
        {
            return _provider();
        }
    } 

    // Bar needs Foo
    public class Bar
    {
        public void Something()
        {
            // you can use the factory here safely
            // but the actual provider is configured elsewhere
            FooFactory factory = new FooFactory();

            IFoo foo = factory.CreateFoo();
        }
    }
}

然后在合成根中的某处(靠近应用程序的入口点)

// kernel is set up to map IFoo to an implementation of your choice
public void ComposeRoot( IKernel kernel )
{
    FooFactory.SetProvider( () => kernel.Get<IFoo>() );
}

如您所见,本地工厂不是可能在多个类中提供多个注入点,而是一个注入点,它提供了整个库的干净配置,使其自包含。

您甚至可以有一个不涉及任何 IoC 容器的提供程序,而是创建一个具体的实现,从而使其无需任何容器即可轻松测试。切换到另一个 IoC 很简单,您只需提供另一个提供商。

你的库越大,你的类越有凝聚力(经常互相使用),拥有一个本地工厂就越方便。你不需要重新抛出你的类之间的依赖关系(没有本地工厂,如果你的类 A 需要 I 并且你的 B 需要 A 然后自动 B 需要 @ 987654329@),而是所有类都依赖于一个工厂。

【讨论】:

  • +1 谢谢 Wiktor,有很多值得考虑的地方。我不能依赖应用程序充当组合根,所以我认为本地工厂可能可以工作,也许是从静态初始化程序配置的。
  • 问题是你不希望你的库直接依赖于 Ninject。那么初始化器必须是外部的。组合根是它的常见位置,是的,我相信你可以依赖它。当调用工厂方法但之前没有调用初始化程序时,您只需抛出异常。异常说“必须在调用工厂方法之前调用初始化程序”。它清楚地表明出了什么问题,因此任何人都可以轻松修复它。
  • 我想得越多,我就越倾向于这种方法。我想我会忘记使用 Ninject,而只是拥有一个带有某种静态初始化程序的工厂类。至少这样我只耦合到工厂类,没有任何外部。我会尝试一下并报告。
  • 是的,你不依赖任何东西,只依赖本地工厂是这种方法的最佳部分。
  • 另一个问题。为什么将 setter 和支持字段设为静态,而将类本身和 getter 设为非静态?
【解决方案2】:

这里有两种情况:

A) 您的消费者没有使用您的 Ninject 容器:

如果您不希望它们能够提供替代配置(或正在注入内部类),则必须创建一个自己解决这些依赖关系的构造函数。这将是您的切入点。

B) 您的消费者正在使用您的 Ninject 容器:

您需要向消费者公开您的 Ninject 内核实例。如果您想隐藏您正在使用 Ninject 的事实,可以将其包装在 ServiceLocator 中,或者只需公开内核本身。无论哪种情况,您都可以将其设为静态类的属性,作为您的入口点。

从内部角度来看,我更喜欢使用选项 B,但作为 3rd 方库的频繁消费者,我从未见过有人公开他们的 IoC 容器,我也不希望他们这样做。我只想能够实例化一个类,而不用担心任何内部实现或依赖关系。当然,您的里程可能会有所不同。

【讨论】:

  • +1 谢谢特罗尔斯。我认为(A)最适合我的情况。我会考虑一段时间。
猜你喜欢
  • 2013-05-08
  • 1970-01-01
  • 2014-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多