假设一个名为 的类Sprinter
:
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;
}
}
从逻辑上讲,必须创建 2 个单元测试类,每种类型一个。
在Sprinter
单元测试中,我最终会得到:
@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) 在Sprinter
课堂上,重复每个测试,但这次基于 aSprinter sprinter = new SuperGenius()
并期望 90 米。=> 什么应该失败,这正是我们想要的!=> 防止违反 Liskov 原则。
2)在SprintGenius
类中,始终基于完全相同的期望添加基于基类的每个测试的类似“克隆”。所以,如果你有 2 个不同的测试,我们最终会得到 4 个测试。2 将 Sprinter 声明为 aSprinter
和 2 声明Sprinter
为SprintGenius
.
3)从不继承具体类(我想这是你阅读这篇文章的第一反应:)),如果合适的话,更喜欢组合!这样这个问题就不会发生了。
基于许多开发人员忽略 Liskov 原则并且经常试图从具体类继承而不是使用其他更好的方法(如组合或不同的继承层次结构)这一事实,防止违反 Liskov 替换原则的最佳实践是什么?
我不想因为开发人员从我的书面课程继承(没有告诉我..),将其注入到一个共享的巨大异构Sprinter
列表列表中并以“你好,奇怪的行为!”而给我带来麻烦。和数小时的调试时间......
我当然不想宣布我所有的具体类“最终”:)