次の名前のクラスがあるとし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;
}
}
論理的には、タイプごとに 1 つずつ、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 つの解決策 (最初の 2 つの解決策は、すべてのチームの開発者にルールが通知され、全員が Liskov の概念を十分に習得していなくても、認めて保持する必要があります)
1)Sprinter
クラスでは、各テストを複製しますが、今回Sprinter sprinter = new SuperGenius()
は 90 メートルを想定しています。=> 何が失敗するべきか、これこそまさに私たちが望んでいたことです! => Liskov の原則に違反しないようにします。
2)SprintGenius
クラスでは、まったく同じ期待に基づいて、基本クラスに基づいて各テストの同様の「クローン」を常に追加します。したがって、2 つの異なるテストがある場合、最終的に 4 つのテストになります。2 は Sprinter を として宣言しSprinter
、2 は として宣言Sprinter
しSprintGenius
ます。
3) 具象クラスから継承することはありません (この投稿を読んで最初に反応したのはこれだと思います:))。この問題が起こらないように。
多くの開発者が Liskov 原則を無視し、コンポジションや異なる継承階層などの別のより良い方法を使用する代わりに、具象クラスから継承するように誘惑されることが多いという事実に基づいて、Liskov 置換原則違反を防ぐためのベストプラクティスは何ですか?
開発者が私が書いたクラスを (私に言わずに..) 継承し、それを異種混合Sprinter
リストの共有された巨大なリスト内に挿入し、「こんにちは、奇妙な振る舞いだ!」そして何時間ものデバッグ時間...
もちろん、すべての具象クラスを「final」と宣言したくありません:)