リスコフの置換原則(Liskov Substitution Principle)をわかりやすく説明する技術資料


リスコフの置換原則とは

リスコフの置換原則(LSP)は、オブジェクト指向プログラミングの設計原則の一つであり、以下のように定義されます:

「親クラス(またはインターフェース)のインスタンスを子クラスで置き換えても、プログラムの動作が変わらないようにするべき」

簡単に言えば、サブクラスは必ず親クラスの振る舞いを継承すべきということです。


ポイント

  • 子クラスは親クラスの性質を保つ。
  • 子クラスで独自の振る舞いを追加する場合でも、親クラスが期待する動作を壊してはいけない。
  • これにより、コードの信頼性や保守性が向上します。

具体例:ゲーム内のキャラクター設計

ゲーム内で、プレイヤーが複数のキャラクター(戦士や魔法使い)を操作できるシステムを考えます。

  1. 親クラスとしてのキャラクター(Character
    • すべてのキャラクターが共通で持つ「移動」「攻撃」などのメソッドを定義。
  2. サブクラス(WarriorMage
    • 戦士(Warrior)や魔法使い(Mage)といった具体的なキャラクターの振る舞いを実装。

この例では、親クラスで期待される動作をサブクラスが壊さないように設計することが重要です。


サンプルコード

using System;

namespace LiskovSubstitutionPrincipleGame
{
    // 1. キャラクターの共通基底クラス
    public abstract class Character
    {
        public abstract void Move();
        public abstract void Attack();
    }

    // 2. 戦士クラス(親クラスの振る舞いを正しく継承)
    public class Warrior : Character
    {
        public override void Move()
        {
            Console.WriteLine("戦士が前進した!");
        }

        public override void Attack()
        {
            Console.WriteLine("戦士が剣で攻撃した!");
        }
    }

    // 3. 魔法使いクラス(親クラスの振る舞いを正しく継承)
    public class Mage : Character
    {
        public override void Move()
        {
            Console.WriteLine("魔法使いが浮遊しながら移動した!");
        }

        public override void Attack()
        {
            Console.WriteLine("魔法使いが魔法で攻撃した!");
        }
    }

    // 4. キャラクター操作クラス
    public class Game
    {
        public void Play(Character character)
        {
            character.Move();
            character.Attack();
        }
    }

    // 実行用クラス
    class Program
    {
        static void Main(string[] args)
        {
            Game game = new Game();

            Character warrior = new Warrior();
            Character mage = new Mage();

            Console.WriteLine("=== 戦士のターン ===");
            game.Play(warrior);

            Console.WriteLine("\n=== 魔法使いのターン ===");
            game.Play(mage);
        }
    }
}

コードの特徴

  1. 親クラス(Character
    • Move()Attack() という共通のメソッドを抽象メソッドとして定義。
    • 子クラスがこれらを実装しなければなりません。
  2. サブクラス(WarriorMage
    • 親クラスで定義されたメソッドの振る舞いを適切に実装。
    • これにより、Character 型のオブジェクトとして WarriorMage を扱っても動作が壊れません。
  3. リスコフの置換原則の適用
    • 親クラスで期待される動作(Move()Attack())がサブクラスでも守られているため、Character 型での操作が問題なく動作します。

実行結果

=== 戦士のターン ===
戦士が前進した!
戦士が剣で攻撃した!

=== 魔法使いのターン ===
魔法使いが浮遊しながら移動した!
魔法使いが魔法で攻撃した!

リスコフの置換原則を破る例

以下のような設計だと、リスコフの置換原則を破ってしまいます。

例: サブクラスで不適切な動作

public class BrokenMage : Character
{
    public override void Move()
    {
        throw new NotImplementedException("魔法使いは移動できません");
    }

    public override void Attack()
    {
        Console.WriteLine("魔法使いが魔法で攻撃した!");
    }
}

この場合、Character 型のオブジェクトで BrokenMage を扱うと、移動時に例外が発生し、親クラスで期待される動作が破壊されます。


リスコフの置換原則の重要性

  • コードの信頼性向上
    子クラスを親クラスとして扱えることで、プログラム全体の一貫性が保たれます。
  • 拡張性の確保
    新しい子クラスを追加しても、既存のロジックに影響を与えません。
  • 保守性の向上
    親クラスを基準にコードを設計すれば、意図しない動作の発生を防げます。

まとめ

リスコフの置換原則(LSP)は、親クラスやインターフェースを基準とした設計を維持することで、コードの信頼性と保守性を高める重要な原則です。
ゲーム開発などのオブジェクト指向設計において、子クラスは必ず親クラスの振る舞いを尊重することを意識して設計しましょう。