C# 接口依赖关系

C# 接口依赖关系,c#,oop,C#,Oop,当您创建一个接口并且知道您将依赖于另一个接口时,您是否将构造函数作为接口的一部分 在我的例子中,我想创建 一个IClientReceiveRecorder,我可以将它提供给客户端,并为短测试会话收集所有网络流量。它可能只是包含一组字符串 一个IEEvaluator,可以获取接收到的所有消息并实现我们想要测试的各种东西。例如,我可能有一个具体的计算器来测试是否所有字符串都包含字母“c” 我知道任何IEEvaluator都需要IClientReceiveRecorder来获取要评估的消息 因此,我看

当您创建一个接口并且知道您将依赖于另一个接口时,您是否将构造函数作为接口的一部分

在我的例子中,我想创建

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

一个IEEvaluator,可以获取接收到的所有消息并实现我们想要测试的各种东西。例如,我可能有一个具体的计算器来测试是否所有字符串都包含字母“c”

我知道任何IEEvaluator都需要IClientReceiveRecorder来获取要评估的消息

因此,我看到了一些选择。 我会做类似的事情吗

interface IEvaluator
{
    IEvaluator(IClientReceiveRecorder);

    void Evaluate();
} 
interface IEvaluator
{
    void Evaluate(IClientReceiveRecorder);
}
它不能编译,所以我猜不会

也许我会做类似的事情

interface IEvaluator
{
    IEvaluator(IClientReceiveRecorder);

    void Evaluate();
} 
interface IEvaluator
{
    void Evaluate(IClientReceiveRecorder);
}

或者,我只是让具体类在其构造函数中获取IClientReceiveRecorder吗?

直截了当的答案是将实现细节留给具体类

知道
ieevaluator
的实现将依赖于
IClientReceiveRecorder
是一个实现细节,不会包含在
ieevaluator
接口中

如果您知道该接口将由另一个接口组成,则可以执行以下操作:

interface IEvaluator
{
    IClientReceiveRecorder { get; set; }

    void Evaluate();
}

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

您真的不需要这样做

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

您可以做的一件事是在显示依赖关系的接口中公开一个属性getter。这至少意味着实现类型需要公开具有预期类型的getter,而预期类型恰好是依赖项


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

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

到目前为止,您还不知道对
ieevaluator
接口的任何进一步使用。如果您将界面保留为如下所示:

public IEvaluator
{
    void Evaluate();
}
然后,这是一个非常灵活的接口,可以为将来想要实现求值语义的任何类重用。这与接口隔离原则和单一责任原则相联系,有助于保持类的解耦

因此,与其在
ieevaluate
接口中包含
IClientReceiveRecorder
,只需通过构造函数传递它:

public ConcreteEvaluator : IEvaluate
{
    private readonly IClientReceiveRecorder recorder;

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

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

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

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

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

public class ClassThatDependsOnSomething
{
    private readonly IDependsOnThis _dependsOnThis;

    public ClassThatDependsOnSomething(IDependsOnThis dependsOnThis)
    {
        _dependsOnThis = dependsOnThis;
    }
}
然后类与它所依赖的接口进行交互,而不负责创建接口-调用其构造函数。这使它真正解耦。如果我的类调用构造函数,这意味着它知道一个具有一个构造函数的实现与另一个具有不同构造函数的实现之间的区别。在这一点上,它与实现相耦合

此外,如果
IDependsOnThis
的实现有其自身的依赖关系,该怎么办?如果
ClassThatDependsOnSomething
调用实现的构造函数并传入这些依赖项,那么这些依赖项将从何处获得?它也必须创造它们

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


这样做的一个关键好处是,由于类依赖于传递到其构造函数中的接口,因此可以为该类编写单元测试并传递“mock”或“test double”,这是实现接口并为方法调用提供虚拟响应的非常简单的类。通过精确控制所有依赖项的作用,您可以测试您的类的行为是否完全符合预期。

接口没有构造函数。那么,我如何表达预期的依赖项?您所描述的是实现方面的问题,应该留待具体实现来决定。其他明智的做法是放弃接口,使用抽象类。