在构造函数中组合依赖项和值
本文关键字:依赖 组合 构造函数 | 更新日期: 2023-09-27 18:15:25
我经常遇到这样的问题:一个类既需要依赖项又需要初始化一些字符串值。例如,考虑以下代码:
public class CustomerService
{
public CustomerService(ITcpConnection connection, string realm)
{
}
}
它的问题是,由于额外的字符串参数,它不容易从DI容器中取出。解决这个问题的最好方法是什么?以下是我能想到的方法:
- Initialize方法(它不够好,因为它提供了Mark Seeman的定义的时间耦合,我同意这不是一个很好的方式) 抽象工厂(我真的不需要额外的间接层,因为参数是一个配置,我知道它在我做配置的时候)
- 在示例中使用,并在DI容器 中注册类的实例
最好避免在同一个构造函数中混合基本配置值和(服务)依赖项。它们使DI配置复杂化,并且经常导致不可读和脆弱的组合根。
改为:
选项1:抽象配置值
当将相同的配置值注入多个类时,通常会丢失一个抽象。一个很好的例子是将string connectionString
值注入到所有存储库中。在这种情况下,应用程序可能缺乏IConnectionFactory
抽象(或类似的东西)。不是将连接字符串注入到许多类中,而是将连接字符串注入到IConnectionFactory
实现中(可能只将该连接字符串作为其构造函数参数),并让其他服务依赖于IConnectionFactory
而不是string
。这样,这些服务就不必处理连接的创建;连接工厂可以这样做。
选项2:将配置值封装到配置类型
基本类型在解析类型时会导致歧义。您注入的string
是文件路径还是连接字符串?你注入的int
,是重试次数,还是顾客购买物品的最低年龄?
为了防止歧义,最好将类的所有配置值包装到它自己的配置类型中,即使该类只需要一个值。以包装TimeSpan
的SqlUnitOfWorkSettings
为例。
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>();