我很难搞清楚何时应该注入依赖项。让我们从我的项目中使用一个简单的例子:
class CompanyDetailProvider : ICompanyDetailProvider {
private readonly FilePathProvider provider;
public CompanyDetailProvider(FilePathProvider provider) {
this.provider = provider;
}
public IEnumerable<CompanyDetail> GetCompanyDetailsForDate(DateTime date) {
string path = this.provider.GetCompanyDetailFilePathForDate(date);
var factory = new DataReaderFactory();
Func<IDataReader> sourceProvider = () => factory.CreateReader(
DataFileType.FlatFile,
path
);
var hydrator = new Hydrator<CompanyDetail>(sourceProvider);
return hydrator;
}
}
(非生产质量!)
ICompanyDetailProvider
负责为消费者提供CompanyDetail
s的实例。具体实现CompanyDetailProvider
通过使用CompanyDetail
从文件中保存Hydrator<T>
的实例来实现它,T
使用反射来填充来自IDataReader
的{{1}}实例。显然,CompanyDetailProvider
依赖于DataReaderFactory
(它返回给定文件路径的OleDbDataReader
实例)和Hydrator
。是否应注入这些依赖关系?注入FilePathProvider
是正确的吗?我应该检查什么品质来决定是否应该注射它们?
答案 0 :(得分:2)
我通过意图/机制镜头评估依赖关系的使用点:这段代码是清楚地传达了它的意图,还是我必须从一堆实现细节中提取它?
如果代码确实看起来像一堆实现细节,我确定输入和输出并创建一个全新的依赖项来表示所有 how 背后的 why 。然后,我将复杂性推向新的依赖关系,使原始代码更简单,更清晰。
当我阅读这个问题中的代码时,我清楚地看到基于日期检索文件路径,然后是一组不透明的语句,这些语句没有清楚地传达读取特定类型实体的目标一条道路。我可以通过它来解决这个问题,但这会打破我的步伐。
我建议你在获得路径后提高计算后半部分的抽象级别。我首先定义一个实现代码输入/输出的依赖项:
public interface IEntityReader
{
IEnumerable<T> ReadEntities<T>(string path);
}
然后,使用这个有意图的界面重写原始类:
public sealed class CompanyDetailProvider : ICompanyDetailProvider
{
private readonly IFilePathProvider _filePathProvider;
private readonly IEntityReader _entityReader;
public CompanyDetailProvider(IFilePathProvider filePathProvider, IEntityReader entityReader)
{
_filePathProvider = filePathProvider;
_entityReader = entityReader;
}
public IEnumerable<CompanyDetail> GetCompanyDetailsForDate(DateTime date)
{
var path = _filePathProvider.GetCompanyDetailsFilePathForDate(date);
return _entityReader.ReadEntities<CompanyDetail>(path);
}
}
现在你可以对血腥的细节进行沙盒化,这些细节在孤立中变得非常有凝聚力:
public sealed class EntityReader : IEntityReader
{
private readonly IDataReaderFactory _dataReaderFactory;
public EntityReader(IDataReaderFactory dataReaderFactory)
{
_dataReaderFactory = dataReaderFactory;
}
public IEnumerable<T> ReadEntities<T>(string path)
{
Func<IDataReader> sourceProvider =
() => _dataReaderFactory.CreateReader(DataFileType.FlatFile, path);
return new Hydrator<T>(sourceProvider);
}
}
如本例所示,我认为您应该将数据读取器工厂抽象出来并直接实例化水化器。区别在于EntityReader
使用数据读取器工厂,而只有创建水合器。它根本不依赖于实例;相反,它是一个水化工厂。
答案 1 :(得分:1)
这个类是否需要外部依赖?
如果是,请注入。
如果不是,则没有依赖。
回答“注入FilePathProvider是否正确?”是的,这是对的。
编辑:为了澄清任何外部依赖关系是您调用不相关但依赖的类尤其是,当它涉及物理资源(如从磁盘读取文件片段)时,但是这也意味着任何类型的服务或模型类都会根据类的核心功能执行逻辑。
通常,只要您调用new运算符,就可以推测出这一点。在大多数情况下,当需要处理除数据传输对象之外的任何类时,您希望重构新运算符的所有用法。当类在使用位置的内部时,如果新的语句降低了复杂性,例如新的DataReaderFactory(),那么新语句就可以了。但是,这似乎也是构造函数注入的一个非常好的候选者。
答案 2 :(得分:1)
我倾向于注入依赖关系的更自由的一面所以我肯定想要注入IDataReader来摆脱新的DataFactoryReader和Hydrator。它使一切都松散耦合,这当然使它更容易维护。
立即轻松获得的另一个好处是更好的可测试性。您可以创建IDataReader和Hydrator的模拟,将单元测试与GetCompanyDetailsForDate方法隔离开来,而不必担心datareader和hydrator内部会发生什么。