接口依赖性

时间:2018-01-11 17:16:33

标签: c# oop

当你创建一个界面并且你知道你将依赖另一个界面时,你是否将构造函数作为界面的一部分?

在我的情况下,我想创建

我可以提供给客户端的IClientReceiveRecorder,并为短暂的测试会话收集所有网络流量。它可能只包含一组字符串。

IEvaluator可以获取收到的所有消息并实现我们想要测试的各种内容。例如,我可能有一个具体的Evaluator来测试所有字符串是否包含字母' c'。

我知道任何IEvaluator都需要一个IClientReceiveRecorder来获取它要评估的消息。

所以,我看到了一些选择。 我做的事情

interface IEvaluator
{
    IEvaluator(IClientReceiveRecorder);

    void Evaluate();
} 

它没有编译,所以我猜不是。

也许我会做像

这样的事情
interface IEvaluator
{
    void Evaluate(IClientReceiveRecorder);
}

或者,我是否只是将其留给具体类来在其构造函数中使用IClientReceiveRecorder?

4 个答案:

答案 0 :(得分:5)

直截了当的答案是将实施细节留给具体课程。

知道IEvaluator的实现将具有IClientReceiveRecorder的依赖关系是一个实现细节,不会包含在IEvaluator接口中。

如果您知道界面将由另一个界面组成,您可以执行以下操作:

interface IEvaluator
{
    IClientReceiveRecorder { get; set; }

    void Evaluate();
}

如何填充该属性是一个实现细节,不应该是界面的一部分。

答案 1 :(得分:4)

你真的不需要。

界面不一定是依赖关系的契约,它是功能的契约。任何实现都可以通过构造函数公开其依赖项。但是,如果不同的实现具有不同(或没有)依赖关系,它仍然实现接口并公开功能。

这至少意味着实现他们需要暴露具有预期类型的​​getter所需的类型,这恰好是一个依赖。

另一种替代方法可能是使用抽象类而不是接口。 (当然,要理解它们之间的差异。)抽象类也可以使用构造函数来公开依赖项,而实现类型则必须使用该构造函数。这个替代方案可能需要在整个代码中进行其他更改,因此由您决定。

答案 2 :(得分:0)

我会在界面中提及IClientReceiveRecorder。正如其他人提到的那样,它是一个实现细节。

除此之外,您还不知道IEvaluator界面可能具有的任何进一步用途。如果保持界面如下:

public IEvaluator
{
    void Evaluate();
}

然后这是一个非常灵活的接口,可以重用于任何想要在将来实现评估语义的类。这与接口隔离原则和单一责任原则相关联,有助于保持您的类分离。

因此,不要在IClientReceiveRecorder接口中包含IEvaluate,只需通过构造函数传递它:

public ConcreteEvaluator : IEvaluate
{
    private readonly IClientReceiveRecorder recorder;

    public ConcreteEvaluator(IClientReceiveRecorder recorder)
    {
        this.recorder = recorder;
    }

    public void Evaluate()
    {
        this.recorder.DoSomeRecording();
    }
}

答案 3 :(得分:0)

接口不具备构建器,正如您已经发现的那样。这是因为使用接口的目的是让您的类与其他类的契约进行交互,而不是实际的实现。该交互是在接口中定义的,但实际实现的创建方式不是该接口的一部分。理想情况下,一个类不应该知道它所依赖的抽象是如何创建的。

这就是构造函数注入有助于保持类解耦的原因。

如果我这样做,这是一种非常常见的模式:

public class ClassThatDependsOnSomething
{
    private readonly IDependsOnThis _dependsOnThis;

    public ClassThatDependsOnSomething(IDependsOnThis dependsOnThis)
    {
        _dependsOnThis = dependsOnThis;
    }
}

然后该类与它所依赖的接口交互而不负责创建它 - 调用它的构造函数。这使它真正脱钩。如果我的类调用构造函数,这意味着它知道具有一个构造函数的一个实现与具有不同构造函数的另一个实现之间的区别。那时它与实现相结合。

另外,如果IDependsOnThis的实现有自己的依赖项怎么办?如果ClassThatDependsOnSomething调用实现的构造函数并传入这些依赖项,那么将从哪里获取这些依赖项?它也必须创造它们。

这就是我们使用依赖注入容器的原因。为了过度简化,DI容器允许我们声明所有实现将用于我们使用的所有接口。然后我们可以要求容器解析接口的实现。如果该实现具有需要更多接口的构造函数,则它会解析这些接口。等等,等等。因此,当使用一个类时,您只需要关注那个类及其所依赖的接口,但是您不必考虑这些接口的实现细节。

这样做的一个主要好处是,由于一个类依赖于传递给它的构造函数的接口,你可以为类编写单元测试并传入"模拟"或者"测试双打,"非常简单的类,它们实现接口并为方法调用提供虚拟响应。通过精确控制所有依赖项的作用,您可以测试您的类的行为与预期完全一样。