Liskov Substitution Principle是否也适用于实现接口的类?

时间:2012-09-03 18:06:29

标签: design-patterns liskov-substitution-principle

1)LSP是否也适用于接口,这意味着我们应该能够使用实现特定接口的类并仍能获得预期的行为?

2)如果确实如此,那么为什么编程接口被认为是一件好事(顺便说一句 - 我知道对接口的编程会增加松散耦合),如果反对使用继承的主要原因之一是由于不遵守LSP的风险?也许是因为:

a)松散耦合的好处超出了不遵守LSP的风险

b)与继承相比,类(实现接口)不会附加到LSP的可能性要小得多

谢谢

3 个答案:

答案 0 :(得分:6)

  

LSP是否也适用于接口,这意味着我们应该能够使用实现特定接口的类并仍能获得预期的行为?

LSP适用于合同。合同可以是一个类或一个接口。

  

如果情况确实如此,那么为什么编程接口被认为是好事(顺便说一句 - 我知道对接口的编程会增加松散耦合),如果反对使用继承的主要原因之一是由于风险不符合LSP?也许是因为:

这不是关于接口或类。这是违反合同的。假设您在Break()(或IVehicle)中使用VehicleBase方法。任何人打电话都会期望车辆坏掉。如果其中一个实现没有破坏,想象一下这个惊喜。这就是LSP的全部内容。

  

a)松散耦合的好处超出了不遵守LSP的风险

EHH?

  

b)与继承相比,类(实现接口)不会附加到LSP的可能性要小得多

EHH?

您可能需要阅读我的SOLID文章,以便更好地理解原则:http://blog.gauffin.org/2012/05/solid-principles-with-real-world-examples/

<强>更新

  

详细说明 - 使用继承虚拟方法可能会使用私有成员,这些子类会覆盖这些虚拟方法无法访问的子类。

是。非常好。成员(字段)应始终受到保护(=声明为私有)。只有定义它们的类才真正知道它们的值应该是什么。

  

此外,派生类从父级继承上下文,因此可以通过将来对父类等的更改来打破。

这违反了开放/封闭原则。即通过改变行为来改变类合同。类应该扩展而不是修改。当然,它不可能一直存在,但改变不应该使类的行为不同(除了错误修正)。

  

因此,我觉得让子类尊重合同比让实现接口的类更难以实现它更难

有一个共同的原因,为什么通过继承扩展很难。这是因为关系不是真正的is-a关系,而是开发人员只想利用基类功能。

那是错的。那么最好使用成分。

答案 1 :(得分:1)

我正在将我的代码实践与LSP进行比较/对比。我认为必须完全关注期望并决定如何定义应该预期的内容。我不相信替代总是意味着同样的事情。例如,如果我定义了

interface ICalculation 
{ 
    double Calculate(double A, double B); 
} 

打算定义class Add : ICalculationclass Subtract : ICalculationclass Multiply : ICalculationclass Divide : ICalculation,我相信这些类应该执行相应的Calculation(A, B),但是,我不相信他们都应该通过相同的单元测试。我喜欢使用接口来实现可扩展性,其中每个扩展都有不同的功能。在汽车制动的情况下,我会给类IBrakable接口并执行此操作:

var myBrakableCar = MyCar as IBrakable;
if(myBrakableCar != null) 
{
    myBrakableCar.Brake();
}

我认为课程的使用应该是可预测的,但也很有用。有用的是顶部。

我只是想创造一个新的概念 - “各自的替代品”:它在锡上做了它所说的。

答案 2 :(得分:0)

广告1):是的。但是,很难让接口强制执行合同,而且他们确实应该这样做。

Ad 2):针对接口进行编程是一种权衡。正如您所指出的那样,接口鼓励所有接口违反LSP,并且具有非平凡的合同。

我相信这些是您问题的答案,或者至少我认识到这些问题是未经回答的问题,这些问题对鼓励使用界面提出了挑战。

我一直使用接口(在C#中)因为我喜欢做TDD。然后我只希望我的模拟和相应的实现不违反LSP,因为当他们做我的测试套件时不再是声音(它宣称有效,但事实并非如此)。

偶尔,我会创建一个抽象基类而不是接口。抽象基类只是强制执行契约,并委托给虚拟保护方法,这些方法应该定义实际实现或在单元测试期间被模拟。事实证明,这在某些应用中非常有用,在这些应用中发现了单元测试本身的错误。

但是我们在C#中遇到了对多重继承的缺失支持,我们仍然坚持使用接口(或者只是更多的类,如果选择这种方法,你可以隐藏在SRP后面)