1) LSP是否也适用于接口,这意味着我们应该能够使用实现特定接口的类,并且仍然能够获得预期的行为?
2)如果确实如此,那么为什么编程到接口被认为是一件好事(BTW-我知道编程到接口会增加松散耦合),如果反对使用继承的主要原因之一是由于不遵守的风险LSP?也许是因为:
a) 松耦合的好处大于不遵守LSP的风险
b)与继承相比,类(实现接口)不附着于LSP的可能性要小得多
谢谢你们
广告1):是的。然而,很难让接口强制执行合同,它们确实应该这样做。
Ad 2):针对接口编程是一种权衡。正如您所指出的,对于具有非平凡契约的所有接口,接口鼓励违反LSP。
我相信这些就是你问题的答案,或者至少我认识到tese是挑战鼓励使用接口的未回答问题。
我一直使用接口(在C语言中),因为我喜欢做TDD。然后,我只希望我的mock和相应的实现不会违反LSP,因为当它们这样做时,我的测试套件就不再可靠了(它声称有些东西可以工作,但事实并非如此)。
偶尔,我会创建一个抽象基类,而不是一个接口。抽象基类只是执行契约,并委托给虚拟的受保护方法,这些方法应该定义实际的实现或在单元测试期间被模拟。这在某些应用程序中被证明是有用的,在这些应用程序中,单元测试本身会发现错误。
但随后我们遇到了C#中缺少的对多重继承的支持,并且我们仍然被接口所困扰(或者仅仅是更多的类,如果您选择这种方法,您可以隐藏在SRP后面)
我现在正在比较我的代码实践和LSP。我认为这一切都与期望有关,并决定如何定义应该期望什么。我不相信替换总是意味着同一件事。例如,如果我定义
interface ICalculation
{
double Calculate(double A, double B);
}
为了定义类Add: ICalculation
,类Subtra: ICalculation
,类Multiply: ICalculation
和类Divide: ICalculation
,我认为这些类应该执行相应的计算(A,B)
然而,我不认为他们都应该通过相同的单元测试。我喜欢使用可扩展性接口,其中每个扩展都有不同的功能。在汽车制动的情况下,我会给类的接口IBrakable和这样做:
var myBrakableCar = MyCar as IBrakable;
if(myBrakableCar != null)
{
myBrakableCar.Brake();
}
我相信类的使用应该是可预测的,但也是有用的。有用的是最重要的。
我将创建一个新的概念——“各自的子状态”:它按照tin上的说明执行。
LSP是否也适用于接口,这意味着我们应该能够使用实现特定接口的类,并且仍然能够获得预期的行为?
LSP适用于本合同。契约可以是类或接口。
如果确实是这样,那么如果反对使用继承的主要原因之一是不遵守LSP的风险,那么为什么对接口编程被认为是一件好事(顺便说一句,我知道对接口编程会增加松耦合)?也许是因为:
这与接口或类无关。这是关于违反合同的。假设您在一个IVeHill
(或车辆库
)中有一个Break()
方法。任何打电话的人都会预料到车会坏。想象一下,如果其中一个实现没有中断,会有多惊讶。这就是LSP的意义所在。
a) 松耦合的好处大于不遵守LSP的风险
嗯?
b) 与继承相比,类(实现接口)不附着到LSP的可能性要小得多
嗯?
您可能想阅读我的文章,以便更好地理解这一原则:http://blog.gauffin.org/2012/05/solid-principles-with-real-world-examples/
使现代化
为了详细说明-带继承的虚拟方法可能会使用私有成员,而重写这些虚拟方法的子类无权访问私有成员。
是的。成员(字段)应该始终被保护(=声明为私有)。只有定义它们的类才真正知道它们的值应该是什么。
此外,派生类继承了父类的上下文,因此可以通过将来对父类的更改等来打破上下文。
这违反了开放/封闭原则。i、 e.通过改变行为来改变课堂契约。类应该扩展,而不是修改。当然,这不是一直都可能的,但是更改不应该使类的行为有所不同(除了错误修复)。
因此,我觉得让子类尊重契约比让类实现接口尊重契约更难
为什么通过继承进行扩展很困难,有一个共同的原因。这是因为这种关系不是真正的is-a
关系,而是开发人员只想利用基类功能。
那是错的。那么最好使用合成。
给定以下类: 当我使用实例化类时: ...然后我只能调用方法,而不能调用方法,除非我强制转换: 当然,为了确保此运行时的安全性,我还应该使用复选框将其包装起来: 我知道我可以有一个包装器接口来扩展这两个接口,但是我最终可以有多个接口来满足可以由同一个类实现的所有可能的接口排列。所讨论的接口不能自然地相互扩展,因此继承似乎也是错误的。 当我询问运行时实例以确定其实现时,/cast方法是否会破坏LSP
不幸的是,由于Liskov替换原则,Java中的子类在接受什么方法参数方面不可能比基类更有限制性,所以Java也允许一些永远不可能为真的无谓比较(并可能导致非常微妙的bug): 另一个不幸的副作用是,正如Josh Bloch很久以前在《Effective Java》中指出的那样,在存在子类型的情况下,基本不可能按照其契约正确实现方法(如果在子类中引入额外的字段,实现将违反契约的对称性和/或传递性要
我发现很难理解这个概念。我脑子里有几个问题。我试着在网上查询,但是没有太多的资源。 子类是否需要在其整个生命周期中保持其独特性? 我很确定LSP定义了超级类和子类之间的契约,如果我错了,请纠正我。 如果一个给定的函数使用某个对象,你能用它的一个子类替换这个对象而不破坏它的执行吗? 如果有一个类型为超类的变量,程序是否仍然有效。如果我将该超类或任何子类的实例放入该变量中。 如果这没有道理,我很抱歉。
我是OOP的新手。最近我读到了关于Liskov替换原理的文章。 在下面给出的代码中,Square类继承Give_区域。假设Square类与Square相关(比如有效性检查)。Give_Area给出正方形的面积(4个顶点位于圆的周长上)和圆的面积。所以,如果给我一个半径,我必须打印圆和正方形的面积(由放置在圆周长上的顶点组成)。为了得到圆的面积,我使用了一个参数。但在求平方面积时没有参数。因此,我在
Liskov替换原则指出,您应该编写您的类继承,这样将子类型交换为它们的基类型就不会改变应用程序的行为。 然而,虚拟关键字字面上似乎存在,以允许子类型的行为不同于基类型。虚拟/覆盖关键字(不包括覆盖抽象成员)的大多数使用不可能违反Liskov吗?我觉得这可能比我理解的更微妙。也许这是一个“规则有时会被打破”的情况,或者原则中的“不要有不同的行为”部分有一个灰色地带。