在类库中初始化IoC容器的好策略是什么?
本文关键字:策略 是什么 类库 初始化 IoC | 更新日期: 2023-09-27 18:13:27
我正在编写一个类库(c#),它将与我无法控制的应用程序一起分发。我的库也有点安全敏感,因此我不想允许调用应用程序为类库做任何依赖配置。它必须是自包含的,并初始化自己的依赖项。
同时,我希望它是可单元测试的和松散耦合的,并且我想使用IoC容器来管理依赖关系。目前,我正在使用内部构造函数和[InternalsVisibleTo()]
,以便单元测试可以进行手动注入。我更喜欢使用Ninject作为我的IoC容器,但我认为这与问题无关。
我正在努力想出一个好的策略来在生产中初始化我的IoC容器,因为类库并没有真正定义入口点,它有许多可以实例化的类,无法知道应用程序将首先使用哪个。
我想知道是否可能有某种AssemblyLoad
事件,确实AppDomain
似乎有这样的事件,但我的程序集必须已经加载到AppDomain之前,我甚至可以钩到它,所以我总是错过由我自己的程序集被加载引发的事件。我还考虑过使用静态初始化器或构造函数,但我不愿意用IoC容器设置污染每个可能的类,因为这会使它们与容器紧密耦合。我不想解耦我的代码,然后把它耦合到IoC容器。
我发现了一些其他的问题讨论这个话题,但没有一个真正处理我的情况。一个建议使用静态初始化器,另一个建议应用程序应该始终是组合的根。
还有别的办法吗?
您的需求之间存在矛盾。
首先,出于安全考虑,您不希望使用组合根。其次,您希望依赖项由容器解析。但是由于标准库不控制容器,所以几乎任何东西都可以注入到代码中,包括试图从内部破坏任何东西的东西。
一种方法是明确你的依赖项,这样库客户端就负责提供你的依赖项。
namespace Library
{
public class Foo1
{
// a classical IoC dependendency
public Foo1( IBar bar )
{
}
}
}
另一方面,在初始化库时使用一个显式外部点的Composition根并不意味着库被污染了。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>() );
}
正如您所看到的,Local Factory不是在多个类中提供多个注入点,而是一个单一的注入点,它提供了整个库的干净配置,使其成为自包含的。
您甚至可以有一个不涉及任何IoC容器的提供者,而是创建一个具体的实现,从而使它在没有任何容器的情况下易于测试。切换到另一个IoC很简单,你只需要提供另一个提供者。
你的库越大,你的类越内聚(经常相互使用),使用本地工厂就越方便。你不需要在你的类之间重新抛出依赖(没有本地工厂,如果你的类A
需要I
,你的B
需要A
,然后自动B
需要I
),相反,你所有的类依赖于一个单一的工厂。
这里有两种情况:
A)你的消费者没有使用你的Ninject容器:如果你不希望它们能够提供替代配置(或者注入内部类),你必须创建一个构造函数来自己解决这些依赖关系。这将是你的入口。
B)你的消费者正在使用你的Ninject容器:你需要向你的消费者公开你的Ninject内核实例。如果你想隐藏正在使用Ninject的事实,可以将其包装在ServiceLocator中,或者直接暴露内核本身。在任何一种情况下,你都可以把它作为一个静态类的属性,作为你的入口点。
从内部角度来看,我更喜欢使用选项B,但作为第三方库的频繁消费者,我从未见过任何人公开他们的IoC容器,我也不希望他们这样做。我只是希望能够实例化一个类,而不担心任何内部实现或依赖关系。当然,你的里程可能会有所不同。