【问题标题】:Creation Of Object That Uses Inversion Of Control创建使用控制反转的对象
【发布时间】:2009-12-29 15:53:27
【问题描述】:

我正在创建一个 CSV 阅读器(是的,我知道 Fast CSV Reader 和 FileHelpers)。 CsvReader 类使用 CsvParser 类来解析 CSV 文件。我想让 CsvReader 类可单元测试,所以我希望能够在外部设置使用的 CsvParser 类(另外,您可以创建自己的实现)。我也不想创建解析器并在正常使用时传递它。

这就是我想使用它的方式。

var reader = new CsvReader( "path/to/file.csv" );

执行此操作时,我可以在 CsvReader 的构造函数中创建 CsvParser,并具有更改解析器的属性。

public ICsvParser Parser { get; set; }

public CsvReader( filePath )
{
    Parser = new CsvParser( filepath );
}

但是当单元测试时,总是创建默认解析器,我只想测试 CsvReader。

解析器可以传递给构造函数,但我不想在正常使用时单独创建解析器。这似乎是一个工厂的好地方。

这似乎是使用 IOC 时的常见问题。有什么好的解决方案?

【问题讨论】:

    标签: c# dependency-injection inversion-of-control


    【解决方案1】:

    解决方案是重写CsvReader 的构造函数以接受ICsvParser 的实现,并且ICsvParser 的具体实现应该有一个构造函数接受其依赖项(要解析的文件的路径)并且已经构造的ICsvParser 应该注入到CsvReader 的构造函数中:

    public CsvReader(ICsvParser parser) {
        this.Parser = parser;
    }
    

    ICsvParser 应该已经被构造为接受其依赖项(要解析的文件的路径)。

    因此:

    // path is string containing path to file to parse
    ICsvParser parser = new SomeCsvParser(path);
    ICsvReader reader = new CsvReader(parser);
    

    重点是CsvReader 不需要路径,它只需要CsvParser。此外,CsvReader 不需要知道 CsvParser 的依赖关系(它需要一个文件路径来解析),以免它也依赖这些依赖关系。

    new 在构造函数内部是一种气味。

    【讨论】:

    • 我同意这一点,但我不希望库的使用者在创建阅读器时必须创建解析器。是否最好有另一个构造函数,我只传递路径并创建解析器并为其使用给定的路径?还是使用一个接受文件路径并创建阅读器和解析器并返回阅读器的工厂会更好?
    • 如果您不希望用户必须创建解析器,那么是的,您最好的选择是拥有一个吃路径并返回构造的ICsvReader 的工厂。正如我所描述的,您仍然应该设置CsvParserCsvReader。我说new里面的构造函数很小。第二个味道是构造函数内部的对象图构造。也就是说,CsvReader 的构造函数不应该接受其他对象 (CsvParser) 的依赖项并使用它来构造自己。那份工作应该委托给一家工厂。 SRP 等等。
    • 太棒了。我最初是这样设置的,但我不喜欢要求将解析器传递给构造函数以供正常使用。我将使用工厂来轻松/默认创建,一切都应该很好。
    • 您对在 CsvReader 上放置一个返回 ICsvReader 的静态 Create 方法 VS 创建一个单独的 CsvReaderFactory 类来进行创建有何想法?没有类型被传入,也没有决策发生,所以一个完整的工厂类似乎有点多。
    • @Josh:该解决方案与我向您建议的解决方案具有相同的质量 - 它遵循 YAGNI 规则(“您还不需要它”)。如果不需要完整的工厂类,最好先写一个简单的工厂方法,以后觉得工厂类更好,再重构。
    【解决方案2】:

    如果这是 IoC,您将传入 CsvParser 而不是路径。

    【讨论】:

      【解决方案3】:

      通常,您会通过构造函数传入解析器并使用 IoC 容器创建对象,然后会为您将解析器实例注入构造函数。

      在单元测试中,您可以直接传入模拟解析器,例如new CsvReader(new MockParser()) 或通过为您注入模拟的测试配置配置您的 IoC。

      【讨论】:

        【解决方案4】:

        创建两个构造函数,一个将解析器接口作为参数,一个仅使用filePath。让第二个创建CsvParser 并让它用这个对象调用第一个。然后您的测试代码可以使用第一个构造函数并传递一个模拟 CsvParser。

        此解决方案有一个缺点:包含 CsvReader 的程序集必须引用包含 CsvParser 的程序集。您必须自己决定是否适合您的情况。

        【讨论】:

          猜你喜欢
          • 2017-01-15
          • 1970-01-01
          • 1970-01-01
          • 2012-05-15
          • 1970-01-01
          • 1970-01-01
          • 2023-03-21
          • 2012-05-15
          • 2010-12-23
          相关资源
          最近更新 更多