3

我经常遇到一个问题,即有一个类既需要依赖项又需要初始化一些字符串值。例如考虑以下代码:

public class CustomerService
{
   public CustomerService(ITcpConnection connection, string realm)
   {
   }
}

它的问题在于,由于附加的字符串参数,将其从 DI 容器中取出并不容易。解决此问题的最佳方法是什么?这些是我能想到的方式:

  1. Initialize 方法(这还不够好,因为它提供了 Mark Seeman 定义的时间耦合,我同意这不是一个很好的方法)
  2. 抽象工厂(我真的不需要额外的间接层,因为参数是一个配置,我在做配置的时候就知道了)
  3. 与示例中一样使用并在 DI 容器中注册类的实例
4

1 回答 1

3

最好避免在同一个构造函数中混合原始配置值和(服务)依赖项。它们使 DI 配置复杂化,并且经常导致不可读和脆弱的Composition Root

请改为执行以下操作:

选项 1:抽象配置值

当将相同的配置值注入多个类时,通常会缺少抽象。一个很好的例子是将string connectionString值注入所有存储库。在这种情况下,应用程序可能缺少IConnectionFactory抽象(或类似的东西)。与其将连接字符串注入许多类,不如将连接字符串注入IConnectionFactory实现(可能只将该连接字符串作为其构造函数参数)并让其他服务依赖IConnectionFactory而不是string. 这样,这些服务就不必处理连接的创建;连接工厂可以做到这一点。

选项 2:将配置值包装到配置类型中

在解析类型时,原始类型会导致歧义。您被注入,是string文件路径还是连接字符串?您正在注入的,是int要重试的次数,还是客户购买商品的最低年龄?

为了防止歧义,最好将一个类的所有配置值包装到它自己的配置类型中,即使该类只需要一个值。SqlUnitOfWorkSettings以包装 a的 this 为例TimeSpan

public sealed class SqlUnitOfWorkSettings
{
    public readonly TimeSpan ConnectionTimeout;

    public SqlUnitOfWorkSettings(TimeSpan connectionTimeout)
    {
        this.ConnectionTimeout = connectionTimeout;
    }
}

而不是依赖于TimeSpanSqlUnitOfWork现在可以依赖于SqlUnitOfWorkSettings

public sealed class SqlUnitOfWork : IUnitOfWork
{
    private readonly SqlUnitOfWorkSettings settings;

    public SqlUnitOfWork(SqlUnitOfWorkSettings settings)
    {
        this.settings = settings;
    }
}

由于没有歧义,新配置类的使用简化了在您的 DI 容器中的注册:

container.RegisterInstance(
    new SqlUnitOfWorkSettings(connectionTimeout: TimeSpan.FromSeconds(30));
container.Register<IUnitOfWork, SqlUnitOfWork>(Lifestyle.Scoped);

选项 3:创建子类型

如果不能使用配置类(例如,因为您无法更改源代码),您可以派生一个放置在您的组合根中的子类型,并定义正确的构造函数并注册该子类型:

public class MyService : SomeExternalService
{
    public MyService(ISomeDependency dep) : base(dep, "My Config Value") { }
}

// Registration
container.Register<IService, MyService>();
于 2013-05-27T12:17:36.313 回答