SimpleInjector ctor注入混合了注册类型和简单值
本文关键字:类型 简单 注册 ctor 注入 混合 SimpleInjector | 更新日期: 2023-09-27 17:58:07
如何注册以另一个注册类型为参数的类型以及简单类型(如整数)
public interface IDeviceManager
{
// implementation omitted.
}
public class DeviceManager : IDeviceManager
{
public DeviceManager(IDeviceConfigRepository configRepo, int cacheTimeout)
{
// implementation omitted
}
}
我确实有IDeviceConfigRepository
的容器注册。没关系。但我如何创建一个具有配置的依赖项的DeviceManager
实例,并在composition root中传递我选择的整数?
我想过创建一个工厂。
public class DeviceManagerFactory : IDeviceManagerFactory
{
private readonly Container _container;
public DeviceManagerFactory(Container container)
{
_container = container;
}
public DeviceManager Create(int minutes)
{
var configRepo = _container.GetInstance<IDeviceConfigurationRepository>();
return new DeviceManager(configRepo, minutes);
}
}
这很简单
然而,现在我还没有注册DeviceManager,这是我最终需要的类型。我应该改为将这些依赖关系更改为工厂吗?
public class ExampleClassUsingDeviceManager
{
private readonly DeviceManager _deviceManager;
public ExampleClassUsingDeviceManager(DeviceManager deviceManager, ...)
{
_deviceManage = deviceManager;
}
// actions...
}
为了实现这一点并避免循环依赖,我可能不得不将工厂从组合根所在的"应用程序"项目(而不是类库)转移到实现DeviceManager的项目。
可以吗?这当然意味着绕过集装箱
对此还有其他解决方案吗?
编辑在其他类型的同一项目中,我使用参数对象将配置注入到对象图中。这工作正常,因为每个参数对象类型只有一个类实例。如果我必须将不同的参数对象实例(例如MongoDbRepositoryOptions
)注入到不同的类实例(例如,MongoDbRepository
)中,我将不得不使用某种命名注册——SimpleInjector不支持这种注册。即使我只有一个整数,参数对象模式也能解决我的问题。但我对这种模式不太满意,因为我知道一旦我有多个消费类实例(即MongoDB Repository),它就会崩溃。
示例:
MongoDbRepositoryOptions options = new MongoDbRepositoryOptions();
MongoDbRepositoryOptions.CollectionName = "config";
MongoDbRepositoryOptions.ConnectionString = "mongodb://localhost:27017";
MongoDbRepositoryOptions.DatabaseName = "dev";
container.RegisterSingleton<MongoDbRepositoryOptions>(options);
container.RegisterSingleton<IDeviceConfigurationRepository, MongoDbRepository>();
我很高兴听到您如何最好地处理在composition root上完成的配置。
让DeviceManagerFactory
依赖于Container
是可以的,只要工厂实现是Composition Root的一部分。
另一种选择是将IDeviceConfigRepository
注入到DeviceManagerFactory
中,这样就可以在不需要访问容器的情况下构建DeviceManager
:
public class DeviceManagerFactory : IDeviceManagerFactory {
private readonly IDeviceConfigurationRepository _repository;
public DeviceManagerFactory(IDeviceConfigurationRepository repository) {
_repository = repository;
}
public DeviceManager Create(int minutes) {
return new DeviceManager(_repository, minutes);
}
}
然而,现在我没有DeviceManager的注册,这是我最终需要的类型。我应该改为将这些依赖关系更改为工厂吗?
总的来说,我想说工厂通常是错误的抽象,因为它们使消费者复杂化,而不是简化它们。因此,您通常应该依赖于服务抽象本身(而不是依赖于可以产生服务抽象实现的工厂抽象),或者您应该注入某种代理或中介,从消费者的角度完全隐藏服务抽象的存在。
@DavidL指出了我关于运行时数据的博客文章。不过,我不确定cacheTimeout
是否是运行时数据,尽管您似乎在这样使用它,因为您正在将它传递到工厂的Create
方法中。但我们这里缺少了一些上下文来确定发生了什么。尽管我的博客文章仍然有效,如果它是运行时数据,那就是反模式,在这种情况下,你应该
- 通过API的方法调用传递运行时数据
或
- 从允许解析运行时数据的特定抽象中检索运行时数据
更新
如果您使用的值是一个应用程序常量,它是通过配置文件读取的,并且在应用程序的生存期内没有更改,那么通过构造函数注入它是完全可以的。在这种情况下,不是运行时值。也不需要工厂。
有多种方法可以在SimpleInjector中注册,例如,您可以使用委托来注册DeviceManager
类:
container.Register<DeviceManager>(() => new DeviceManager(
container.GetInstance<IDeviceConfigRepository>(),
cacheTimeout: 15));
这种方法的缺点是,您失去了Simple Injector为您自动连接类型的能力,并且您禁用了Simple Injector为你验证、诊断和可视化对象图的能力。有时这很好,而其他时候则不然。
这里的问题是SimpleInjector阻止了基元类型的注册(因为它们会导致歧义),而没有为您提供一种干净的注册方法。我们正在考虑(最后)在v4中添加这样的功能,但这并不能真正满足您当前的需求。
Simple Injector不容易让你指定基元依赖项,同时让容器自动连接其余的。Simple Inject的IDependencyInjectionBehavior
抽象允许你覆盖默认行为(即不允许这样做)。这里描述了这一点,但我通常建议不要这样做,因为这通常需要相当多的代码。
这里基本上有两种选择:
- 从类中抽象出处理这种缓存的特定逻辑,并将其封装在一个新的类中。这个类将只有
cacheTimeout
作为它的依赖项。当然,这只有在实际需要抽象的逻辑时才有用,而且通常只有在将原始值注入多个消费者时才是逻辑的。例如,与其将connectionstring
注入多个类,不如将IConnectionFactory
注入这些类 - 将
cacheTimeout
值包装到特定于消费类的复杂数据容器中。这使您能够注册该类型,因为它解决了歧义问题。事实上,这就是你自己已经在建议的,我认为这是一件非常好的事情。由于这些值在运行时是恒定的,所以将DTO注册为singleton是可以的,但要确保它是不可变的。当您为每个消费者提供自己的数据对象时,您不必注册这些对象的多个实例,因为它们是唯一的。顺便说一句,虽然不支持命名注册,但您可以使用RegisterConditional
进行条件注册或上下文注册,还有其他方法可以使用Simple Injector实现命名注册,但是我认为您在这里并不真正需要