Liskov Substitution Principle (LSP) の日本語解説
LSP (Liskov Substitution Principle) は、オブジェクト指向プログラミング (OOP) の SOLID 原則の一つであり、サブタイプがスーパタイプのインスタンスと置き換え可能であるべきことを示しています。
定義
- サブタイプ は、スーパタイプから継承されたメソッドをオーバーライドしても、その振る舞いがスーパタイプのメソッドの契約を満たさなければならない。
- 契約 とは、メソッドの入力値、出力値、および副作用に関する期待事項のこと。
例
interface Shape {
double area();
}
class Rectangle implements Shape {
private double width;
private double height;
// ...
public double area() {
return width * height;
}
}
class Square extends Rectangle {
private double side;
// ...
public double area() {
return side * side;
}
}
この例では、Square
は Rectangle
のサブタイプです。しかし、Square
の area()
メソッドは、Rectangle
の area()
メソッドの契約を満たしていません。なぜなら、Rectangle
の width
と height
は独立しているのに対し、Square
の side
は両方に依存しているためです。
そのため、Rectangle
型の変数に Square
オブジェクトを代入して、area()
メソッドを呼び出すと、意図しない結果が生じる可能性があります。これは、LSP の原則に違反しているためです。
LSP を守るための方法
- サブタイプはスーパタイプの契約を厳密に遵守する
- スーパタイプは抽象的なインターフェースを提供する
- サブタイプはスーパタイプの振る舞いを拡張するのではなく、変更しない
LSP の基本と例: コード解説
LSP の基本
例: 矩形と正方形
interface Shape {
double area();
}
class Rectangle implements Shape {
private double width;
private double height;
// ...
public double area() {
return width * height;
}
}
class Square extends Rectangle {
private double side;
// ...
public double area() {
return side * side;
}
}
この例では、Shape
インターフェースが共通の契約を定義し、Rectangle
と Square
クラスがその契約を実装しています。
public void processShape(Rectangle rect) {
// 矩形を処理する
rect.setWidth(5);
rect.setHeight(10);
System.out.println("面積: " + rect.area());
}
このコードでは、processShape
メソッドが Rectangle
型の引数を受け取っています。しかし、もし Square
オブジェクトをこのメソッドに渡すと、問題が発生します。
Square square = new Square(5);
processShape(square); // 問題が生じる
Square
クラスは Rectangle
のサブタイプですが、setWidth
と setHeight
メソッドは Square
クラスの side
属性を変更するべきです。しかし、Rectangle
クラスの setWidth
と setHeight
メソッドは、Square
クラスの side
属性を適切に更新しません。
LSP を守るためには、サブタイプがスーパタイプの契約を厳密に遵守する必要があります。以下の例では、Square
クラスが Rectangle
クラスの契約を遵守するように修正しています。
class Square extends Rectangle {
private double side;
public Square(double side) {
this.side = side;
}
@Override
public void setWidth(double width) {
this.side = width;
}
@Override
public void setHeight(double height) {
this.side = height;
}
}
LSP の代替アプローチ
Liskov Substitution Principle (LSP) を守るための代替アプローチとして、以下の方法が考えられます。
インターフェースの分割
- 共通のインターフェース を定義し、LSP を守る必要があるメソッドのみを含める。
- サブタイプ は、この共通のインターフェースを実装する。
抽象クラスの使用
- 抽象クラス を定義し、LSP を守る必要があるメソッドを実装する。
- サブタイプ は、この抽象クラスを継承し、必要に応じてメソッドをオーバーライドする。
- 抽象クラスは、LSP を守るために必要な制約を課すことができる。
委譲の使用
- サブタイプ は、スーパタイプのインスタンスをフィールドとして持つ。
- サブタイプのメソッドは、スーパタイプのメソッドを委譲する。
- これにより、サブタイプがスーパタイプの契約を直接遵守する必要がなくなる。
例: 矩形と正方形の代替アプローチ
interface Shape {
double area();
}
interface RectangleSpecific {
void setWidth(double width);
void setHeight(double height);
}
class Rectangle implements Shape, RectangleSpecific {
// ...
}
class Square implements Shape {
// ...
}
abstract class Rectangle implements Shape {
// ...
}
class Square extends Rectangle {
// ...
}
委譲の使用
class Square implements Shape {
private Rectangle rect;
// ...
}
oop definition solid-principles