C#
 Computer >> コンピューター >  >> プログラミング >> C#

リスコフの置換原則(LSP)とは?C#での実装方法をわかりやすく解説

派生型は、その基底型と完全に置き換え可能でなければなりません。これがリスコフの置換原則(Liskov Substitution Principle:LSP)の核心です。LSPはSOLID原則の「L」に相当し、オブジェクト指向における継承の正しい使い方を定める重要な指針となっています。

リスコフの置換原則の定義

リスコフの置換原則とは、「子クラス(派生クラス)を、あたかも親クラス(基底クラス)であるかのように扱えるべきである」という原則です。本質的には、すべての派生クラスが親クラスの機能を保持し、親クラスが提供する機能を勝手に置き換えてしまってはならない、ということを意味します。

この原則を守ることで、次のようなメリットが得られます。

  • 基底クラス型の変数に派生クラスのインスタンスを代入しても、プログラムが正しく動作する
  • ポリモーフィズムを安全に活用できる
  • 継承階層の保守性と拡張性が向上する

原則に違反しているコード例(Before)

ここでは、幾何学における「楕円」と「円」を例に見てみましょう。数学的には「円は楕円の特殊なケース」であるため、Circle クラスを Ellipse クラスから継承させるのは一見自然に思えます。

public class Ellipse {
    public double MajorAxis { get; set; }
    public double MinorAxis { get; set; }

    public virtual void SetMajorAxis(double majorAxis) {
        this.MajorAxis = majorAxis;
    }
    public virtual void SetMinorAxis(double minorAxis) {
        this.MinorAxis = minorAxis;
    }
    public virtual double Area() {
        return MajorAxis * MinorAxis * Math.PI;
    }
}

public class Circle : Ellipse {
    public override void SetMajorAxis(double majorAxis) {
        base.SetMajorAxis(majorAxis);
        this.MinorAxis = majorAxis; // 円では両軸の長さが同一
    }
}

public class Result {
    public void Method1() {
        Circle circle = new Circle();
        circle.SetMajorAxis(5);
        circle.SetMinorAxis(4);
        var area = circle.Area(); // 5 × 4 × π になってしまうが、本来は 5 × 5 × π を期待
    }
}

このコードの問題点は、Circle.SetMajorAxis()MinorAxis も同じ値で上書きしてしまうことにあります。そのため、SetMajorAxis(5) の後に SetMinorAxis(4) を呼び出すと、長軸と短軸が異なる状態になり、面積は 5 × 4 × π となってしまいます。円である以上、どちらの軸も同じ長さであるべきなのに、メソッドの呼び出し順序によって結果が変わってしまう――これがLSP違反の典型例です。

原則に準拠したコード例(After)

この問題を解決するには、無理に継承関係を作らないことが有効です。「円は楕円の一種」という数学的な分類にとらわれず、それぞれを独立したクラスとして設計し直します。

public class Ellipse {
    public double MajorAxis { get; set; }
    public double MinorAxis { get; set; }

    public virtual void SetMajorAxis(double majorAxis) {
        MajorAxis = majorAxis;
    }
    public virtual void SetMinorAxis(double minorAxis) {
        MinorAxis = minorAxis;
    }
    public virtual double Area() {
        return MajorAxis * MinorAxis * Math.PI;
    }
}

// 継承をやめ、円を独立したクラスとして設計する
public class Circle {
    public double Radius { get; set; }

    public void SetRadius(double radius) {
        this.Radius = radius;
    }
    public double Area() {
        return this.Radius * this.Radius * Math.PI;
    }
}

Circle クラスを Ellipse から継承させず、半径(Radius)だけを持つ独立したクラスにすることで、「軸の設定順序によって振る舞いが変わる」という矛盾がなくなりました。これにより、意図しない副作用を引き起こすことなく、安全にコードを利用できます。

別のアプローチ:不変(Immutable)な設計

どうしても共通の基底クラスが必要な場合は、オブジェクトを不変(イミュータブル)にする方法もあります。コンストラクタで長軸・短軸を一度に設定し、プロパティを読み取り専用にすれば、生成後に状態が変わることがなく、LSP違反を防ぐことができます。

まとめ

リスコフの置換原則は、「派生クラスが基底クラスの契約を破っていないか」を常に自問するための原則です。継承は「is-a関係」を安易に信じ込まず、派生クラスが基底クラスと完全に置き換え可能かどうかを検証してから採用することが大切です。C#では、virtual / override によるメソッドの上書きが基底クラスの契約を壊していないか、特に注意して設計しましょう。

  1. Windows 10の「Godモード」とは?特徴と有効化の手順をわかりやすく解説

    目次:Windows 10のGodモード概要Godモードとは何かWindows 10でGodモードを有効化する方法Windows 10のGodモード概要Windowsシステム上のあらゆるツールや設定項目に、素早く簡単にアクセスしたいと思ったことはありませんか?そんなときに便利なのが、Windowsマスターコントロールパネル(ブログ界隈では「Godモード」とも呼ばれています)です。これを有効化すれば、設定へのアクセスが格段に楽になります。実はこの機能は特定のフォルダ内に隠されているため、その存在自体を知らないユーザーも多く、実際にWindows 10で設定したことがある方はさらに少数でしょう。こ

  2. BIOSとは?仕組みとWindows 10での正しい更新方法を徹底解説

    パソコンでキーボードの不具合、電源まわりのトラブル、インターネット接続の問題や動作速度の低下などが発生したとき、その原因がBIOSに関連しているケースは少なくありません。修理業者やITの専門家に相談すると、本格的なトラブルシューティングの前に「まずBIOSを更新してみてください」とアドバイスされることも多いでしょう。実際、BIOSの更新だけで問題が解決するケースも多くあります。 BIOSとは? BIOS(Basic Input/Output System:基本入出力システム)は、PCのマザーボード上にある小さなメモリチップに格納されたソフトウェアで、CPUやGPUなど、PCに搭載されたすべての