假设一个名为sprint
的类:
public class Sprinter {
protected int travelMeters;
public void run(int seconds) {
this.travelMeters = 9 * seconds;
}
public int getTravelMeters(){
return travelMeters;
}
}
和一个SprintGenius
类型继承Sprinter
:
class SprintGenius extends Sprinter {
public void run(int seconds) {
this.travelMeters = 10 * seconds;
}
}
从逻辑上讲,必须创建两个单元测试类,每种类型一个。
在sprint
单元测试中,我最终会得到:
@Before
public void setUp() {
Sprinter sprinter = new Sprinter();
}
public void testSprinterShouldRun90metersWithin10Seconds() {
sprinter.run(10);
assertEquals(sprinter.getTraveledMeters(),90);
}
在SprintGenius
单元测试中,我会得到:
@Before
public void setUp() {
Sprinter sprinter = new SprintGenius();
}
public void testSprinterShouldRun100metersWithin10Seconds() {
sprinter.run(10);
assertEquals(sprinter.getTraveledMeters(),100);
}
在上面的两个测试中,我将测试10秒内的行驶量。
显然,这两种测试都是绿色的。
然而,违反利斯科夫替代原则又如何呢?
事实上,任何客户端代码都应该期望任何短跑运动员在9秒内跑10米。
3个解决方案(前两个解决方案向所有团队的开发人员发出规则信号,即使不是每个人都很好地掌握了Liskov的概念,也必须承认和保留)
1) 在sprint
课程中,重复每次测试,但这次基于sprint-sprint=new SuperGenius()
,预计90米=
2) 在SprintGenius
类中,始终根据完全相同的期望,基于基类添加每个测试的类似“克隆”。所以,如果你有两个不同的测试,我们最终会有4个测试。2宣布Sprinter为Sprinter
,2宣布Sprinter
为SprintGenius
。
3)永远不要继承具体类(我想这是你读这篇文章的第一反应:)),如果合适,更喜欢作文!这样这个问题就不会发生。
基于这样一个事实,即许多开发人员忽略了Liskov原则,并且经常试图从具体类继承,而不是使用另一种更好的方式,如组合或不同的继承层次结构,防止Liskov替换原则违反的最佳实践是什么?
我不想因为开发人员继承了我编写的类而麻烦(没有告诉我…),将它注入到一个共享的大量异构sprint
列表中,面对我说“你好,奇怪的行为!”还有几个小时的调试时间。。。
当然,我不想将我所有的具体类都声明为“final”:
这并没有违反Liskov替代原则的合规性。这就是糟糕的设计。你的两个类在行为上没有不同,但在数据上有不同。所以,你应该只有一个级别,客户应该期望任何短跑运动员按照速度跑给定的距离。
因此,您应该添加速度属性,并使用具有具体行为的单个类。
之后,您可以考虑使用新的行为创建真实的扩展类,并考虑测试。
有了这个速度参数,对于其他类型的跑步者,即使他们以不同的速度跑步,也不应违反利斯科夫替代原则。
你的问题是:我的类扩展了一个人却没有通过测试,因为我把一个人的名字从“彼得”改为“罗伯特”。
这是此类问题的一个坏例子。举个恰当的例子,我认为是的,这是一个很好的实践来测试它,但这是一种极端防御的方法。也许你可以利用你的时间更好地创建测试。此外,该测试将在很短的时间内过时,很难为任何新的子类添加测试来确保旧的行为正常工作。
单元测试是关于特定模块的测试,不能也不应该用于比这更广泛的东西。符合Liskov替换原则是系统范围内的一个更广泛的问题,而不是模块范围。此外,它不是代码中要测试的东西。这是一个纯粹的设计问题,与实现无关。我不认为LSP可以通过自动工具强制执行。它应该在设计审查期间处理,然后在代码审查期间处理(应该检查是否符合设计)。
我发现很难理解这个概念。我脑子里有几个问题。我试着在网上查询,但是没有太多的资源。 子类是否需要在其整个生命周期中保持其独特性? 我很确定LSP定义了超级类和子类之间的契约,如果我错了,请纠正我。 如果一个给定的函数使用某个对象,你能用它的一个子类替换这个对象而不破坏它的执行吗? 如果有一个类型为超类的变量,程序是否仍然有效。如果我将该超类或任何子类的实例放入该变量中。 如果这没有道理,我很抱歉。
问题内容: 我听说Liskov替代原理(LSP)是面向对象设计的基本原理。它是什么?有哪些使用示例? 问题答案: 一个很好的例子说明了LSP(我最近听到的一个播客中的Bob叔叔给了LSP),就是有时候听起来有些自然语言在代码中不太起作用。 在数学中,是。实际上,它是矩形的一种特殊形式。“是”使您想使用继承对其进行建模。但是,如果在您编写的代码中Square派生自Rectangle,则aSquare
1) LSP是否也适用于接口,这意味着我们应该能够使用实现特定接口的类,并且仍然能够获得预期的行为? 2)如果确实如此,那么为什么编程到接口被认为是一件好事(BTW-我知道编程到接口会增加松散耦合),如果反对使用继承的主要原因之一是由于不遵守的风险LSP?也许是因为: a) 松耦合的好处大于不遵守LSP的风险 b)与继承相比,类(实现接口)不附着于LSP的可能性要小得多 谢谢你们
不幸的是,由于Liskov替换原则,Java中的子类在接受什么方法参数方面不可能比基类更有限制性,所以Java也允许一些永远不可能为真的无谓比较(并可能导致非常微妙的bug): 另一个不幸的副作用是,正如Josh Bloch很久以前在《Effective Java》中指出的那样,在存在子类型的情况下,基本不可能按照其契约正确实现方法(如果在子类中引入额外的字段,实现将违反契约的对称性和/或传递性要
Java不允许
概述 在工作初期,我们可能会经常会有这样的感觉,自己的代码接口设计混乱、代码耦合较为严重、一个类的代码过多等等,自己回头看的时候都觉得汗颜。再看那些知名的开源库,它们大多有着整洁的代码、清晰简单的接口、职责单一的类,这个时候我们通常会捶胸顿足而感叹:什么时候老夫才能写出这样的代码! 相关资料 iOS AFNetworking2.0源码解析 AFNetworking源码 单一原则(Single Re