Liskov Substitution Principle (LSP) の日本語解説

2024-09-12

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;
    }
}

この例では、SquareRectangle のサブタイプです。しかし、Squarearea() メソッドは、Rectanglearea() メソッドの契約を満たしていません。なぜなら、Rectanglewidthheight は独立しているのに対し、Squareside は両方に依存しているためです。

そのため、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 インターフェースが共通の契約を定義し、RectangleSquare クラスがその契約を実装しています。

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 のサブタイプですが、setWidthsetHeight メソッドは Square クラスの side 属性を変更するべきです。しかし、Rectangle クラスの setWidthsetHeight メソッドは、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

oop definition solid principles

「継承よりも合成を優先する」の日本語解説

**「継承よりも合成を優先する」**という原則は、オブジェクト指向プログラミングにおいて、継承よりも合成を使用することを推奨する設計原則です。定義: あるクラスが別のクラスから特性やメソッドを継承し、そのクラスのサブクラスになる関係。利点: コードの再利用が可能になり、共通の機能を簡単に実装できる。


C++におけるクラスと構造体の使い分け:具体的なコード例

C++では、クラスと構造体はどちらもデータと関数をカプセル化するための手段ですが、その使用目的とデフォルトのアクセス修飾子に違いがあります。デフォルトのアクセス修飾子: private主な用途:オブジェクト指向プログラミング (OOP) における抽象的なデータ型を定義する。データの隠蔽とカプセル化を実現する。継承やポリモーフィズムなどのOOPの概念を活用する。