模拟在生产代码中引入处理


15

假设有一个IReader接口,一个IReader接口ReaderImplementation的实现以及一个使用和处理来自读取器的数据的类ReaderConsumer。

public interface IReader
{
     object Read()
}

实作

public class ReaderImplementation
{
    ...
    public object Read()
    {
        ...
    }
}

消费者:

public class ReaderConsumer()
{
    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // read some data
    public object ReadData()
    {
        IReader reader = new ReaderImplementation(this.location)
        data = reader.Read()
        ...
        return processedData    
    }
}

为了测试ReaderConsumer和处理,我使用了IReader的模拟。因此,ReaderConsumer变为:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // mock constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader
    }

    // read some data
    public object ReadData()
    {
        try
        {
            if(this.reader == null)
            {
                 this.reader = new ReaderImplementation(this.location)
            }

            data = reader.Read()
            ...
            return processedData    
        }
        finally
        {
            this.reader = null
        }
    }
}

在此解决方案中,由于仅模拟构造函数提供了接口的实例,因此模拟为生产代码引入了if语句。

在撰写本文时,我意识到try-finally块在某种程度上是无关的,因为在那里可以处理用户在应用程序运行时更改位置的情况。

总的来说,它闻起来很臭,如何更好地处理?


16
通常,这不是问题,因为注入依赖项的构造函数将是唯一的构造函数。是不是出了问题作出的ReaderConsumer独立ReaderImplementation
克里斯·沃勒特

当前,很难删除依赖关系。通过多看一点,我有一个更深层的问题,不仅仅是对ReaderImplemenatation的依赖。由于ReaderConsumer是在工厂启动过程中创建的,因此在应用程序的生命周期中一直存在,并接受用户的更改,因此需要进行一些额外的处理。可能配置/用户输入可以作为对象存在,然后可以即时创建ReaderConsumer和ReaderImplementation。给出的两个答案都很好地解决了更通用的情况。
kristian mo

3
是的。这就是TDD 的要点:必须首先编写测试,这意味着需要进行更分离的设计(否则,您将无法编写单元测试...)。这有助于使代码更易于维护和扩展。
巴库里

寻找可通过“依赖注入”解决的气味的好方法是寻找关键字“ new”。不要更新您的依赖关系。注入它们。
Eternal21

Answers:


67

不用从您的方法初始化读取器,而是移动此行

{
    this.reader = new ReaderImplementation(this.location)
}

进入默认的无参数构造函数。

public ReaderConsumer()
{
    this.reader = new ReaderImplementation(this.location)
}

public ReaderConsumer(IReader reader)
{
    this.reader = reader
}

没有“模拟构造函数”之类的东西,如果您的类具有工作所需的依赖项,则应该为该构造函数提供该东西或创建它。


3
score ++ ...从DI的角度来看,默认构造函数绝对是代码的味道。
Mathieu Guindon

3
@ Mat'sMug默认实现没有错。缺少ctor链是这里的味道。=;)-–
RubberDuck

哦,是的 -如果您没有这本书,这篇文章似乎很好地描述了混蛋注入反模式(尽管略过了)。
Mathieu Guindon

3
@ Mat'sMug:您是在用教条解释DI。如果您不需要注入默认的构造函数,则不需要。
罗伯特·哈维

3
@ Mat'sMug链接的书还讨论了依赖关系很好的“可接受的默认值”(尽管它指的是属性注入)。让我们这样说:在简化和可维护性方面,二构造方法比一构造方法花费更多,并且为清楚起见过分简化了该问题,虽然成本不合理,但在某些情况下可能是合理的。
卡尔·莱斯

54

您只需要一个构造函数:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader;
    }

在您的生产代码中:

var rc = new ReaderConsumer(new ReaderImplementation(0));

在您的测试中:

var rc = new ReaderConsumer(new MockImplementation(0));

13

研究依赖注入和控制反转

Ewan和RubberDuck都有出色的答案。但是我想提一下要研究的另一个领域,即依赖注入(DI)和控制反转(IoC)。这两种方法都将您遇到的问题转移到框架/库中,因此您不必担心。

您的示例很简单,并且很快就可以免除,但是不可避免地要在它的基础上进行构建,最终会有大量的构造函数或初始化例程如下:

var foo = new Foo(new Bar(new Bar(new Baz(),new Quz()),new Foo2());

使用DI / IoC,您可以使用一个库,该库允许您拼写出将接口与实现相匹配的规则,然后您只需说“给我一个Foo”,就可以弄清楚如何将它们连接起来。

那里有很多非常友好的IoC容器(它们被称为),我将推荐一个容器进行查看,但是,请探索一下,因为有很多不错的选择。

一个简单的开始是:

http://www.ninject.org/

以下是要探索的列表:

http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx


7
Ewan和RubberDuck的答案都证明了 DI。DI / IoC与工具或框架无关,而与对代码进行架构和结构化有关,以使控制反转并注入依赖项。Mark Seemann的书在解释DI / IoC如何在没有IoC框架的情况下完全可以实现,以及为什么何时成为IoC框架成为一个好主意(提示:当您具有..的依赖关系的依赖关系时)方面做得很好(很棒!)。 。)=)
Mathieu Guindon

另外... +1,因为Ninject非常棒,重新阅读后,您的答案似乎还不错。第一段读起来有点像Ewan和RubberDuck的答案不是关于DI的,这正是促使我提出第一条评论的原因。
Mathieu Guindon

1
@ Mat'sMug一定会明白的。我试图鼓励OP查看其他海报实际上正在使用的DI / IoC(Ewan是穷人的DI),但没有提及。当然,使用框架的好处是它可以使用户沉浸在DI的世界中,并提供许多具体的示例,希望这些示例可以指导他们了解自己在做什么……尽管……并非总是如此。:-)当然,我喜欢Mark Seemann的书。这是我的最爱之一。
Reginald Blue

感谢您提供有关DI框架的提示,它似乎是正确地解决这一问题的方法:)
kristian mo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.