解耦问题 - 改进和替代方案

Decoupling issue - improvements and alternatives

我正在学习 SOLID 原则 - 特别是控制反转-DI-解耦,当我审查我的一个代码时,我注意到这种方法(见下文)引起了我的注意。

任何需要读取 json 文件的方法都会调用此代码,接受将用于查找 json 文件的字符串值。但是正如您所看到的(我简化了代码 - 为了本主题排除了异常处理),我不确定从哪里开始(有很多初始化或依赖项??发生了,我不确定在哪里开始)。

这个 method/scenario 可以作为开始的好候选人吗?你认为我应该保留哪个?并且需要解耦?

谢谢。

public async Task<object> ReadJsonByKey(string jsonPath, string jsonKey)
{
    // First - is it okay to have an initialization at this stage?
    var value = new object();     

    // Second - is this fine to have this in the scope of this method?
    using (TextReader reader = File.OpenText(jsonPath))  
    {
        // Third -  Calling Jobject that accepts new instance of JsonTextReader
        var jObject = await JObject.LoadAsync(new JsonTextReader(reader)); 
        obj = jObject.SelectToken(jsonKey);
    }
    return value;
}

我问这个的原因也是因为(基于标准)松散耦合的东西可以很容易地测试 - 即单元测试

[UnitTestSuite]
    [TestCase1]
        // Method should only be able to accept ".json" or ".txt" file

    [TestCase2]
        // JsonPath file is valid file system

    [TestCase3]
        // Method should be able to retrieve a node value based from a specific json and key

    [TestCase4]
        // Json-text file is not empty

您似乎在尝试将基础设施问题与您的应用程序代码分离。

假设是这种情况,您需要一个 class 负责读取数据:

public interface IDataReader
{
     Task<object> ReadJsonByKey(string jsonPath, string jsonKey)
}

其实现将是您上面的代码:

public class DataReader : IDataReader
{
    public async Task<object> ReadJsonByKey(string jsonPath, string jsonKey)
    {
        // First - is it okay to have an initialization at this stage?
        var value = new object();     

        // Second - is this fine to have this in the scope of this method?
        using (TextReader reader = File.OpenText(jsonPath))  
        {
            // Third -  Calling Jobject that accepts new instance of JsonTextReader
            var jObject = await JObject.LoadAsync(new JsonTextReader(reader)); 
            obj = jObject.SelectToken(jsonKey);
        }
        return value;
    }
}

但是这个 class 现在正在同时进行文件读取和反序列化,因此您可以进一步分成:

public class DataReader : IDataReader
{
    IDeserializer _deserializer;

    public DataReader(IDeserializer deserializer)
    {
        _deserializer = deserializer;
    }

    public async Task<object> ReadJsonByKey(string jsonPath, string jsonKey)
    {
        var json =  File.ReadAllText(jsonPath);

        return _deserializer.Deserialize(json, jsonKey);
    }
}

这意味着现在可以独立于文件系统依赖性对您的 IDeserializer 进行单元测试。

但是,主要好处应该是您现在可以在对应用程序代码进行单元测试时模拟 IDataReader 实现。

唯一阻止您对其进行正确单元测试的是 File 引用,它是静态的。您将无法通过文件提供该方法,因为它必须实际存在。有两种方法可以解决这个问题。

首先,如果可能的话,您可以传递其他内容而不是方法的路径 - 例如 FileStream

其次,可以说更好,您可以将文件系统(我建议使用 System.IO.Abstractions 然后使用相关的 TestingHelpers 包)抽象到一个私有字段中,通过 ctor 注入传递依赖关系。

private readonly IFileSystem fileSystem;

public MyClass(IFileSystem fileSystem)
{
    this.fileSystem = fileSystem;
}

然后在你的方法中你会使用

fileSystem.File.OpenText(jsonPath);

这应该允许您通过传递 MockFileSystem 并在内存中创建一个 json 文件供方法读取,从而轻松地对该方法进行单元测试。单元可测试性实际上是一个很好的指标,表明您的方法是可维护的并且具有明确的目的 - 如果您可以使用不那么复杂的单元测试轻松地测试它,那么它可能很好。如果不能,那肯定是坏了。

使函数像:

public async Task<object> ReadJsonByKey(TextReader reader, string jsonKey)

现在该函数适用于任何 TextReader 实现,因此您可以传递从文件、内存或任何其他数据源读取的 TextReader。