X Liskov 置換原則: 親クラスを変更するのではなく、親クラスを継承する
意味
Liskov 置換原則は、サブタイプの特別な定義です。
ソフトウェア エンティティが親クラスに適用できる場合は、そのサブクラスにも適用できなければなりません。親クラスを参照するすべての場所で、そのサブクラスを透過的に使用できる必要があります。オブジェクト、サブクラス オブジェクトプログラムロジックは変更されないまま、親クラスオブジェクトを置き換えることができます
- サブクラスは親クラスの抽象メソッドを実装できますが、親クラスの非抽象メソッドをオーバーライド (オーバーライド) することはできません。
- サブクラスのメソッドが親クラスのメソッドをオーバーライドする場合、メソッドの前提条件 (つまり、メソッドの入力パラメータ) は、親クラスのメソッドの入力パラメータよりも緩くなります (範囲が広くなります)。
- サブクラスのメソッドが親クラスのメソッドを実装する (オーバーライド/オーバーロードする、または抽象メソッドを実装する) 場合、メソッドの事後条件 (つまり、メソッドの戻り値) は親クラスの事後条件よりも厳密であるか、同等になります。
- サブクラスは独自の固有のメソッドまたは属性を持つことができます。つまり、サブクラスは親クラスの機能を拡張できますが、親クラスの元の機能を変更することはできません。
リスコフ置換原則は主に、継承に関するいくつかの原則、つまり、継承を使用する必要がある場合、継承を使用すべきでない場合、およびその基礎となる原則を説明します。リスコフ置換はもともと継承再利用の基礎です. 基本クラスとサブクラスの間の関係を反映します. それは抽象化を達成するための特定のステップの仕様です. それは開始と終了の原則の補足であり、開始を実現するための重要な方法です.そして閉会原則。
本旨
親クラス オブジェクトをそのサブクラス オブジェクトで置き換えた後、プログラムは例外を生成しません。
アドバンテージ
- 制約の継承が蔓延しており、リスコフ置換原理は開閉原理を実現するための重要な方法の 1 つであり、開閉原理を具体化したものです。
- 継承における親クラスのオーバーライドによって引き起こされる再利用性の低下という欠点を克服します。
- それはアクションの正しさの保証です。つまり、クラスの拡張によって既存のシステムに新しいエラーが導入されることはなく、コード エラーの可能性が減ります。
- プログラムの堅牢性を強化すると同時に、変更時の互換性も非常に高く、プログラムの保守性と拡張性が向上します。要件が変化したときに発生するリスクを軽減する
場合
最初のポイント
「継承」で直面する問題の 1 つは、親クラスによって定義されたメソッドが、そのサブクラスにメソッドの実装規則への完全な準拠を強制しないことです。サブクラスは、親クラスから継承したメソッドを変更できます。リスコフ置換原則では、その中心となる考え方を厳密に遵守する必要があります
public class Father {
public void getNum(int a, int b) {
System.out.println("父类getNum()方法计算出的结果为:" + (a + b));
}
}
public class Son extends Father {
@Override
public void getNum(int a, int b) {
System.out.println("子类getNum()方法计算出的结果为:" + (a - b));
}
}
public class Test {
public static void main(String[] args) {
test01();
}
public static void test01(){
Father father = new Father();
father.getNum(5, 10);
father = new Son();
father.getNum(5, 10);
}
}
親クラスの本来の意図は2つの数値を加算するメソッドを定義することでしたが、メソッドを継承した後、サブクラスが減算に変更したところ成功しました。サブクラスがこのように動作すると、継承システム全体に損害を与えることになります。親クラスが使用されている場所をそのサブクラスに置き換えたい場合、元の通常の関数に問題があることがわかります。
この問題を解決するために、リスコフ置換原理の最初のポイントは次のとおりです。
サブクラスは親クラスに抽象メソッドを実装できますが、親クラスの非抽象メソッドをオーバーライド (オーバーライド) することはできません。
2点目
やむを得ず親クラスのメソッドと同名のメソッドをサブクラスに追加する場合は、以下の点を遵守する必要があります。
サブクラスのメソッドが親クラスのメソッドをオーバーライドする場合、メソッドの前提条件 (つまり、メソッドの入力パラメータ) は、親クラスのメソッドの入力パラメータよりも緩くなります (範囲が広くなります)。
この直接の目的の 1 つは、親クラス オブジェクトをサブクラス オブジェクトに置き換えた後のメソッド呼び出しでの混乱を防ぐことです。
// 父类
public void method(ArrayList arrayList) {
System.out.println("父类方法执行了!");
}
// 子类
@Override
public void method(ArrayList arrayList) {
System.out.println("子类方法执行了!");
}
public static void test02(){
ArrayList list = new ArrayList();
Father father = new Father();
Son son = new Son();
System.out.print("使用父类对象调用的结果:");
father.method(list);
System.out.print("将父类对象替换成子类对象后的调用结果:");
son.method(list);
}
親クラスのメソッドのパラメータとサブクラスのメソッドのパラメータを交換すると、
// 父类
public void method(List<String> arrayList) {
System.out.println("父类方法执行了!");
}
// 子类
public void method(ArrayList<String> arrayList) {
System.out.println("子类方法执行了!");
}
私たちの当初の意図は、オブジェクトが置き換えられた後も元のメソッドが引き続き実行されることを期待することでしたが、結果は変わりました。これはリスコフ置換原則と矛盾します。したがって、不要なエラーが発生しないように、サブクラス メソッドのパラメータ範囲が親クラスのパラメータ範囲よりも大きいことを常に念頭に置く必要があります。
3 番目のポイント
親クラスのメソッドをオーバーライドまたは実装する必要がある場合は、次の点に従う必要があります。
サブクラスのメソッドが親クラスのメソッドを実装する (オーバーライド/オーバーロードする、または抽象メソッドを実装する) 場合、メソッドの事後条件 (つまり、メソッドの戻り値) は親クラスの事後条件よりも厳密であるか、同等になります。
// 父类
public List<String> getList() {
return new ArrayList<>();
}
// 子类
@Override
public ArrayList<String> getList() {
return new ArrayList<>();
}
親クラスのメソッドからの戻り値をサブクラスで拡大、オーバーライド、または実装しようとすると、コードはエラーを報告し、基本的なコンパイラーでもそれを渡すことができなくなります。
4番目のポイント
サブクラスは独自の一意のメソッドまたはプロパティを持つことができます
この文は理解しやすく、サブクラスは親クラスから継承したものを持つだけでなく、サブクラス自体を拡張して独自のものを記述することもできることを示しています。
ここまでの説明で、継承においてどのような点に注意し、どのようなルールに従う必要があるかについて、リスコフ置換原理の基本的な概念を皆さんは理解できたと思います。
しかし、実際の開発ではこの原則に違反することが何度もあり、表面的には特に大きな問題はないものの、コードのエラー率が大幅に増加します。