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