如何注入表示文件夹集合的依赖项?



依赖注入文件夹的最佳方法是什么?

我有一个需要三个文件夹的类。目标是从子文件夹结构(包含多个文件夹的文件夹)中收集文件,文件被排序到其中,并将其写入另外两个子文件夹结构,无论是通过抽象还是非抽象,它都需要文件夹。

具体来说,我想将机器学习算法的数据拆分为训练数据和测试数据,而子文件夹代表将要分类的图像的不同类别。

那么,在仍然具有易于测试的代码的同时注入这些文件夹的最佳方法是什么? 我应该只传递一个字符串吗?我应该传递文件信息对象吗?我应该构建一个接口,它代表文件夹结构的包装器吗? 处理此问题的最佳方法是什么?

C# 方法是最好的,但不是必需的。

如果缺少信息,请告诉我。

现在使用 System.IO.Abstractions 表示文件系统操作而不实际依赖于文件系统更容易。该模式类似于我们如何编写依赖于HttpContextWrapper而不是直接依赖于HttpContext的代码,这使我们能够模拟HttpContext

使用这些类,你可以注入IEnumerable<System.IO.Abstractions.DirectoryInfoWrapper>,在运行时,每个目录都是一个"真正的"DirectoryInfo,创建如下:

var directory = new DirectoryInfo("c:folder");
var wrapper = new DirectoryInfoWrapper(new FileSystem(), directory);

DirectoryInfoWrapper的行为与DirectoryInfo类似,只是它也返回抽象。例如,wrapper.GetFiles()返回IFileInfo[]而不是FileInfo[]。因此,我们所有的代码都将被编写为依赖于抽象。这很好,因为抽象具有与具体类相同的属性和方法。

或者,您可能想要这样的东西,而不是注入实际的目录:

public interface IDirectoryProvider
{
IEnumerable<DirectoryInfoWrapper> GetDirectories(string someInput);
}

在任何一种情况下,这都允许您使用模拟目录进行单元测试,如有必要,这些目录包含更多模拟目录甚至模拟文件。我通常不喜欢返回模拟的模拟。您甚至可以让模拟目录返回测试项目中包含的真实文件,如果这比创建模拟文件更容易。至少它提供了一些在抽象之前不可用的选项。

令人

毛骨悚然的细节:有人可能会争辩说,这些并不是真正的"抽象",因为从设计上讲,它们是具体类的精确表示。你可以用它们来表示完全不同的东西,比如数据库存储,但你可能不会,它们也不是很好的抽象,因为它会迫使你将虚假路径映射到记录。

话虽如此,我试图想象我应该如何称呼命名空间而不是System.IO.Abstractions,我想不出更好的东西。你可以称它们为"模拟",但在生产代码中看到它们会令人困惑。

无论您如何编写它,都无法测试在不使用实际文件和文件夹的情况下在文件夹之间移动文件的类。但就表示而言,也许是这样的:

public interface ISomethingRepository
{
IEnumerable<ThingWithDataInIt> GetThings();
void SaveAsTraining(ThingWithDataInIt thing);
void SaveAsTest(ThingWithDataInIt thing);
}

目的是,无论依赖于什么,它都真正想要文件中的内容,并且它想知道在检查了项目后,它可以将其与"训练"数据或"测试"数据一起保存。

实现可以基于文件系统。我只是为了说明而编造细节。我不知道这些文件里有什么,是否需要反序列化等。也许对于每个文件,您必须解析行并返回事物的集合。这是为了说明。

public class FileSystemSomethingRepository : ISomethingRepository
{
private readonly string _sourceDirectoryPath;
private readonly string _trainingDirectoryPath;
private readonly string _testDirectoryPath;
public FileSystemSomethingRepository(string sourceDirectoryPath, 
string trainingDirectoryPath, 
string testDirectoryPath)
{
_sourceDirectoryPath = sourceDirectoryPath;
_trainingDirectoryPath = trainingDirectoryPath;
_testDirectoryPath = testDirectoryPath;
}
public IEnumerable<ThingWithDataInIt> GetThings()
{
var filePaths = Directory.GetFiles(_sourceDirectoryPath);
foreach (var filePath in filePaths)
{
var fileContent = File.ReadAllText(filePath);
var deserialized = JsonConvert.DeserializeObject<ThingWithDataInIt>(fileContent);
yield return deserialized;
}
}
public void SaveAsTraining(ThingWithDataInIt thing)
{
// serialize it, write it to the folder
}
public void SaveAsTest(ThingWithDataInIt thing)
{
// serialize it, write it to the folder
}
}

该接口很容易模拟,并且会让任何依赖于此的类都不知道数据是否来自文件系统,它是如何序列化/反序列化的,等等。 对使用者隐藏这些细节是使其成为抽象并使您能够获得依赖注入的好处的原因。

其他可以帮助您设计正确抽象的东西是编写您的接口,准确描述您希望依赖于它的类与它做什么。换句话说,从消费者的角度编写接口。这样,您就不会试图想象一个解决方案,同时试图弄清楚它是否会做你想要的。您可能需要进行一些调整,但首先要通过编写接口来确定类需要什么。然后你弄清楚如何实现它。

这也使您能够首先专注于最重要的任务。你想要编写机器学习算法,而不是从文件中读取的东西。您可以只编写表示类需要的接口,然后继续前进,就好像实现已经存在一样。你可以专注于你更关心它的东西,你甚至可以测试它。然后,您可以回到编写这样的实现细节。或者,如果你在一个团队中工作,你可以给其他人接口并要求他们实现它。

最新更新