C# 没有完全理解为什么我';d在类中有一个接口,而不是从中继承

C# 没有完全理解为什么我';d在类中有一个接口,而不是从中继承,c#,interface,C#,Interface,我正在浏览坚实的原则,并偶然发现了一些SRP的代码 我有这段代码,但我不知道为什么我会有一个接口,以我下面的方式声明?这对我有什么好处?我发现,它显示了一个解决方案,但它并没有真正解释代码如何更好地工作,或者为什么事情是这样的 我不明白的是: igateutability\u网关实用程序,它的右下方是以IGateUtility为参数的构造函数。为什么这样写?我必须传递的参数是什么 public class ServiceStation { IGateUtility _gateUtilit

我正在浏览坚实的原则,并偶然发现了一些SRP的代码

我有这段代码,但我不知道为什么我会有一个接口,以我下面的方式声明?这对我有什么好处?我发现,它显示了一个解决方案,但它并没有真正解释代码如何更好地工作,或者为什么事情是这样的

我不明白的是:

igateutability\u网关实用程序公共类服务站
中的code>,它的右下方是以
IGateUtility
为参数的构造函数。为什么这样写?我必须传递的参数是什么

public class ServiceStation
{
    IGateUtility _gateUtility;

    public ServiceStation(IGateUtility gateUtility)
    {
        this._gateUtility = gateUtility;
    }
    public void OpenForService()
    {
        _gateUtility.OpenGate();
    }

    public void DoService()
    {
        //Check if service station is opened and then
        //complete the vehicle service
    }

    public void CloseForDay()
    {
        _gateUtility.CloseGate();
    }
}

public class ServiceStationUtility : IGateUtility
{
    public void OpenGate()
    {
        //Open the shop if the time is later than 9 AM
    }

    public void CloseGate()
    {
        //Close the shop if the time has crossed 6PM
    }
}


public interface IGateUtility
{
    void OpenGate();

    void CloseGate();
}

构造函数参数是依赖注入的一个例子,特别是被称为“构造函数注入”的技术(通常是首选技术)

ServiceStation
不应包含
igateutability
的逻辑,因为它与闸门无关(单一责任原则)。但是,它确实需要使用门,以便将实现
IGateUtility
的对象传入

一般来说,我不认为继承在这种情况下是有意义的;但有一项原则规定:

喜欢组合而不是继承


这基本上意味着;注入(组合)对象以获得对其行为的访问,而不是从中继承。

SRP代表单一责任原则。因此这里的服务类没有任何责任创建ServiceStationUtility对象

让我们用一个例子来理解它。假设您没有使用任何接口,那么您的代码如下所示

    public class ServiceStation
    {
      ServiceStationUtility  _gateUtility;

    public ServiceStation()
    {
        this._gateUtility = new ServiceStationUtility();
    }
    public void OpenForService()
    {
        _gateUtility.OpenGate();
    }

    public void DoService()
    {
        //Check if service station is opened and then
        //complete the vehicle service
    }

    public void CloseForDay()
    {
        _gateUtility.CloseGate();
    }
     }

   public class ServiceStationUtility 
   {
       public void OpenGate()
       {
        //Open the shop if the time is later than 9 AM
       }

      public void CloseGate()
      {
        //Close the shop if the time has crossed 6PM
      }
    }
因此,如果不使用接口now,Service Station类还负责创建ServiceStation对象,这违反了SRP

第二个问题

上面的代码是,如果您想为OpenGate()CloseGate()提供不同的实现。您必须创建另一个类。因此,您必须再次更改ServiceStation类中的代码

使用界面的原因

接口允许依赖项注入。这意味着对象创建任务被委托给第三方。这意味着这里ServiceClass不需要知道谁是对象的实际提供者ServiceClass只需跟随即可 这里是接口。与ServiceStationUtility类相同,该类也不知道谁将使用它


因此,接口有助于构建松散耦合的体系结构

在我们到达您当前拥有的类之前,有几个步骤

第一步
ServiceStation
ServiceStationUtility
紧密耦合。因此,我们无法对ServiceStation进行单元测试

public class ServiceStation
{
    ServiceStationUtility _gateUtility;

    public ServiceStation()
    {
        this._gateUtility = new ServiceStationUtility();
    }

    public void OpenForService()
    {
        _gateUtility.OpenGate();
    }

    public void DoService() 
   {
   }

    public void CloseForDay()
    {
        _gateUtility.CloseGate();
    }
}
步骤2 我们通过
构造注入模式
注入依赖项,并以某种方式实现单元测试,但是
ServiceStation
仍然与
ServiceStationUtility
紧密耦合

public class ServiceStation
{
    ServiceStationUtility _gateUtility;

    public ServiceStation(ServiceStationUtility gateUtility)
    {
        this._gateUtility = gateUtility;
    }

    public void OpenForService()
    {
        _gateUtility.OpenGate();
    }

    public void DoService()
    {
        //Check if service station is opened and then
        //complete the vehicle service
    }

    public void CloseForDay()
    {
        _gateUtility.CloseGate();
    }
}
步骤3 Liskov的替换原则-子类应该是可互换的 使用超类

依赖项反转原则-高级组件不应 依赖于低级组件,或不应负责 创建它们。相反,它们应该依赖于 提供给他们

最后是你所拥有的。基本满足SO_ID原则;在这种情况下,不需要满足Liskov的替换原则

public class ServiceStation
{
    IGateUtility _gateUtility;

    public ServiceStation(IGateUtility gateUtility)
    {
        this._gateUtility = gateUtility;
    }

    public void OpenForService()
    {
        _gateUtility.OpenGate();
    }

    public void DoService()
    {
        //Check if service station is opened and then
        //complete the vehicle service
    }

    public void CloseForDay()
    {
        _gateUtility.CloseGate();
    }
}

public interface IGateUtility
{
    void OpenGate();

    void CloseGate();
}
样本单元测试 依赖于接口的类不依赖于其他任何东西。这种抽象实现了松散耦合,并允许我们轻松地实现单元测试

using Moq;
using Xunit;

public class ServiceStationTests
{
    [Fact]
    public void OpenForService_should_call_OpenGate_once()
    {
        // Arrange
        var mockGateUtility = new Mock<IGateUtility>();
        mockGateUtility.Setup(x => x.OpenGate());

        // Act
        var sut = new ServiceStation(mockGateUtility.Object);
        sut.OpenForService();

        // Assert
        mockGateUtility.Verify(x => x.OpenGate(), Times.Once);
    }

    [Fact]
    public void CloseForDay_should_call_CloseGate_once()
    {
        // Arrange
        var mockGateUtility = new Mock<IGateUtility>();
        mockGateUtility.Setup(x => x.CloseGate());

        // Act
        var sut = new ServiceStation(mockGateUtility.Object);
        sut.CloseForDay();

        // Assert
        mockGateUtility.Verify(x => x.CloseGate(), Times.Once);
    }
}
使用Moq;
使用Xunit;
公共类ServiceStationTests
{
[事实]
public void OpenForService\u应该调用\u OpenGate\u once()
{
//安排
var mockGateUtility=new Mock();
mockGateUtility.Setup(x=>x.OpenGate());
//表演
var sut=新服务站(mockGateUtility.Object);
sut.OpenForService();
//断言
验证(x=>x.OpenGate(),Times.Once);
}
[事实]
公共无效关闭日期\u应该\u调用\u CloseGate\u一次()
{
//安排
var mockGateUtility=new Mock();
mockGateUtility.Setup(x=>x.CloseGate());
//表演
var sut=新服务站(mockGateUtility.Object);
sut.CloseForDay();
//断言
验证(x=>x.CloseGate(),次.Once);
}
}

它用于依赖项注入。其想法是,您希望测试您的
ServiceStation
类,而不必使用实际的
ServiceStationUtility
实例,而是使用不同的
IGateUtility
实现,可能是返回人为测试数据的“假”版本。这是另一层抽象。这是一个非常简单的例子,它在costructor中,所以可以注入到这个类中。您可以传递任何实现IGateUtility的类。这就是“is-a”和“has-a”之间的区别。编码到接口,而不是实现。