最好避免在同一个构造函数中混合原始配置值和(服务)依赖项。它们使 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;
}
}
而不是依赖于TimeSpan
,SqlUnitOfWork
现在可以依赖于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>();