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上完成的配置。

SimpleInjector ctor注入混合了注册类型和简单值

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方法中。但我们这里缺少了一些上下文来确定发生了什么。尽管我的博客文章仍然有效,如果它是运行时数据,那就是反模式,在这种情况下,你应该

  1. 通过API的方法调用传递运行时数据

  1. 从允许解析运行时数据的特定抽象中检索运行时数据

更新

如果您使用的值是一个应用程序常量,它是通过配置文件读取的,并且在应用程序的生存期内没有更改,那么通过构造函数注入它是完全可以的。在这种情况下,不是运行时值。也不需要工厂。

有多种方法可以在SimpleInjector中注册,例如,您可以使用委托来注册DeviceManager类:

container.Register<DeviceManager>(() => new DeviceManager(
    container.GetInstance<IDeviceConfigRepository>(),
    cacheTimeout: 15));

这种方法的缺点是,您失去了Simple Injector为您自动连接类型的能力,并且您禁用了Simple Injector为你验证、诊断和可视化对象图的能力。有时这很好,而其他时候则不然。

这里的问题是SimpleInjector阻止了基元类型的注册(因为它们会导致歧义),而没有为您提供一种干净的注册方法。我们正在考虑(最后)在v4中添加这样的功能,但这并不能真正满足您当前的需求。

Simple Injector不容易让你指定基元依赖项,同时让容器自动连接其余的。Simple Inject的IDependencyInjectionBehavior抽象允许你覆盖默认行为(即不允许这样做)。这里描述了这一点,但我通常建议不要这样做,因为这通常需要相当多的代码。

这里基本上有两种选择:

  1. 从类中抽象出处理这种缓存的特定逻辑,并将其封装在一个新的类中。这个类将只有cacheTimeout作为它的依赖项。当然,这只有在实际需要抽象的逻辑时才有用,而且通常只有在将原始值注入多个消费者时才是逻辑的。例如,与其将connectionstring注入多个类,不如将IConnectionFactory注入这些类
  2. cacheTimeout值包装到特定于消费类的复杂数据容器中。这使您能够注册该类型,因为它解决了歧义问题。事实上,这就是你自己已经在建议的,我认为这是一件非常好的事情。由于这些值在运行时是恒定的,所以将DTO注册为singleton是可以的,但要确保它是不可变的。当您为每个消费者提供自己的数据对象时,您不必注册这些对象的多个实例,因为它们是唯一的。顺便说一句,虽然不支持命名注册,但您可以使用RegisterConditional进行条件注册或上下文注册,还有其他方法可以使用Simple Injector实现命名注册,但是我认为您在这里并不真正需要