基礎から学ぶ継承とポリモーフィズム

C# で理解するオブジェクト指向の核心


1. はじめに

オブジェクト指向(OOP)の 4 本柱のうち、継承 (Inheritance) と ポリモーフィズム (Polymorphism) は「共通化」と「拡張性」を支える最重要キーワードです。この記事では、まだ List<T> やラムダ式に触れていない初学者でもイメージしやすいように、動物クラスの例を通じてステップバイステップで解説します。


2. 継承 ―「親の特性を受け継ぐ」

2-1 継承のイメージ

  • 比喩「犬は動物の一種」という自然な関係をそのままコードに落とし込みます。
  • 目的
    1. コード再利用 – 共通コードは親クラスに一度だけ書く
    2. 階層化 – 大枠→詳細へ思考を整理できる

2-2 C# の基本構文

class Animal   // 親(基底)クラス
{
    public string Name { get; set; }

    public void Eat()    // 全動物共通の振る舞い
    {
        Console.WriteLine($"{Name} は食事をする");
    }
}

class Dog : Animal  // 子(派生)クラス
{
    public void Bark()
    {
        Console.WriteLine($"{Name} はワン!と鳴く");
    }
}
  • Dog : Animal の : が 「Animal を継承」 を表します。
  • Dog は Eat() を自動で持ち、独自メソッド Bark() を追加できます。

🛠 使い方(Console アプリ編)

  1. Visual Studio
    • 「新しいプロジェクト」→ コンソール アプリ(.NET 8 以上推奨)を作成
    • Animal.cs, Dog.cs, Program.cs の 3 ファイル構成にする
// Program.cs
using System;
class Program
{
    static void Main()
    {
        var pochi = new Dog { Name = "ポチ" };
        pochi.Eat();   // Animal のメソッド
        pochi.Bark();  // Dog 独自メソッド
    }
}

2-3 継承の注意点

落とし穴具体例回避策
深すぎる階層Animal → Mammal → Canine → Dog → … と 6 段以上3〜4 段で再設計を検討
is-a が成り立たない継承Bird : Airplane など共通化は インターフェースで代替
過剰な protected何でも派生先から触れるprivate とプロパティで公開範囲を最小化

3. ポリモーフィズム ―「同じ呼び出しが多彩に振る舞う」

3-1 ポリモーフィズムとは

  • ギリシャ語で 「多形」
  • 共通の型参照実体に応じたメソッドが動く仕組み。

3-2 virtual / override による動的ポリモーフィズム

class Animal
{
    public string Name { get; set; }

    public virtual void Speak()   // 上書き可
    {
        Console.WriteLine($"{Name} は何か声を出す");
    }
}

class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine($"{Name} はワン!と鳴く");
    }
}

class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine($"{Name} はニャーと鳴く");
    }
}
  • Speak() 呼び出し一つでDog と Cat が異なる鳴き声を出す。
  • 拡張に強い:Bird を追加しても foreach 側は変更不要。

🛠 使い方(Console アプリ編)

  1. 先ほどと同様に Console プロジェクトを用意。
  2. Animal.cs, Dog.cs, Cat.cs, Program.cs を配置:
// Program.cs
using System;
class Program
{
    static void Main()
    {
        Animal[] zoo =
        {
            new Dog { Name = "ポチ" },
            new Cat { Name = "タマ" }
        };

        foreach (var a in zoo)
            a.Speak();   // 実体に応じた出力が得られる
    }
}


実行 →

ポチ はワン!と鳴く
タマ はニャーと鳴く

3-3 インターフェースによるポリモーフィズム

interface IFlyable
{
    void Fly();
}

class Bird : Animal, IFlyable
{
    public override void Speak() => Console.WriteLine($"{Name} はチュンと鳴く");
    public void Fly()            => Console.WriteLine($"{Name} が飛ぶ");
}

class Plane : IFlyable
{
    public void Fly() => Console.WriteLine("飛行機が離陸する");
}
  • 共通点は“飛べる”ことだけ
  • 2 つを IFlyable 配列に入れて Fly() を呼び出せば、実体ごとに適した挙動が発動。


🛠 使い方

using System;
class Program
{
    static void Main()
    {
        IFlyable[] flyers =
        {
            new Bird { Name = "ピー" },
            new Plane()
        };

        foreach (var f in flyers)
            f.Fly();
    }
}

4. 継承 × ポリモーフィズムを Unity で試す

シーン:AnimalController スクリプトを空の GameObject に付けて実行

4-1 スクリプト

// Animal.cs (Unity でも同じクラスを利用)
public class Animal
{
    public string Name { get; set; }
    public virtual void Speak() => Debug.Log($"{Name} は何か声を出す");
}

public class Dog : Animal
{
    public override void Speak() => Debug.Log($"{Name} はワン!");
}

// Bird なども同様...
// AnimalController.cs
using UnityEngine;

public class AnimalController : MonoBehaviour
{
    private void Start()
    {
        Animal[] animals =
        {
            new Dog { Name = "ポチ" },
            new Cat { Name = "タマ" },
            new Bird { Name = "ピー" }
        };

        foreach (var a in animals)
            a.Speak();
    }
}

🛠 使い方(Unity 6)

  1. Unity Hub で 3D Core テンプレートのプロジェクトを新規作成。
  2. Scripts フォルダーを作り、上記 .cs ファイルを保存。
  3. Hierarchy で Create → Empty。オブジェクト名は GameManager など。
  4. AnimalController.cs を GameManager にドラッグ&ドロップ。
  5. Play ボタン → Console に 3 行の鳴き声が表示されれば成功。
  • コンソールに「ポチ はワン!」「タマ はニャー」「ピー はチュン」が順に表示。
  • Unity のスクリプトでも同じ文法で動くことを確認できます。

5. 練習課題

  1. Fish クラスを Animal から継承し、Speak() を「はブクブク…」にする。
  2. IFlyable を実装しているクラスをシーン上でランダムに飛ばすミニゲームを作る。
  3. 「継承よりコンポジション(部品化)が適切」と思われるケースを 3 つ挙げる。

1. Fish クラスを実装し、Speak() を「〇〇はブクブク…」にする

class Fish : Animal
{
    public override void Speak()
    {
        Console.WriteLine($"{Name} はブクブク…");
    }
}

確認コード

var nemo = new Fish { Name = "ニモ" };
nemo.Speak();   // => ニモ はブクブク…

2. IFlyable を実装しているクラスをランダムに飛ばすミニゲーム(Unity)

  1. インターフェースと実装クラス
public interface IFlyable
{
    void Fly(Vector3 direction, float speed);
}

public class Bird : Animal, IFlyable
{
    public override void Speak() => Debug.Log($"{Name} はチュン");
    public void Fly(Vector3 direction, float speed)
        => transform.Translate(direction.normalized * speed * Time.deltaTime);
}

public class Plane : MonoBehaviour, IFlyable
{
    public void Fly(Vector3 direction, float speed)
        => transform.Translate(direction.normalized * speed * Time.deltaTime);
}
  1. コントローラースクリプト空の GameObject に追加してください。
using UnityEngine;

public class FlyManager : MonoBehaviour
{
    [SerializeField] private MonoBehaviour[] flyables; // Bird, Plane など

    private void Start()
    {
        InvokeRepeating(nameof(RandomFly), 1f, 2f); // 2 秒ごとに呼び出し
    }

    private void RandomFly()
    {
        if (flyables.Length == 0) return;

        // ランダムに 1 体取得
        var f = (IFlyable)flyables[Random.Range(0, flyables.Length)];

        // ランダム方向と速度
        Vector3 dir  = Random.onUnitSphere;  // 3D 空間なら
        float   speed = Random.Range(1f, 5f);

        f.Fly(dir, speed);
    }
}

ポイント

  • 配列の型を MonoBehaviour にしているのは Unity エディタでドラッグ&ドロップ登録しやすくするため。
  • 呼び出し側 (FlyManager) は IFlyable しか意識しておらず、Bird を追加してもコード変更不要です。

3. 継承よりコンポジションが適切なケース例

ケース継承では問題になる点コンポジションでの設計イメージ
① 武器を装備できるキャラクターSwordman : Character, Archer : Characterなどを増やすとクラス爆発Character が Weapon を「持つ」。IWeapon インターフェースを介し、動的に交換可能
② UI のボタン装飾RedButton : Button, GreenButton : Button, … 色・大きさごとに派生が増えるButton に Style オブジェクトを注入し、色・枠線・サイズをパラメータ化
③ 複数の機能を組み合わせる家電SmartWasherDryerMicrowave : Applianceのような不自然な is-a 関係小機能(洗う・乾かす・温める)を個別クラス化し、Appliance が必要な機能を「持つ」

判断の目安

  • is-a(~は~である)があいまい / 無理やり
  • 機能の組み合わせパターンが多い
  • ランタイムに振る舞いを差し替えたい

その場合は コンポジション(部品化)+インターフェース を優先しましょう。


まとめ

サンプル回答を動かしながら次を試すと理解が深まります。

  1. Fish を Animal[] に混ぜて Speak() ポリモーフィズムを再確認
  2. IFlyable 実装クラスを増やし、FlyManager はノータッチで拡張できるか検証
  3. 自身のプロジェクトで「継承 vs コンポジション」を意識し、クラス設計を見直す

小さなサンプルでも “変更に強い” を体験することで、オブジェクト指向の本当のメリットが体に染み込みます。


6. まとめ

キーワード一言まとめ
継承 (Inheritance)「親の共通機能を子どもが受け継ぐ」
ポリモーフィズム (Polymorphism)「親型参照で子どもを自在に扱う」
  • 継承は コード再利用と階層化、ポリモーフィズムは 拡張性と柔軟性 をもたらします。
  • 理解のコツは、*「Dog は Animal である」*と *「Animal 配列の中身は何になるかわからない」*の 2 点を意識すること。
  • まずはサンプルを コピペ→実行→改造 し、振る舞いが切り替わる瞬間を体験してみましょう。

次のステップ

  • abstract クラスと sealed クラスの使いどころ
  • インターフェース多重実装と DI (依存性注入) の基本
  • 継承を避けたいケースの「コンポジション vs 継承」比較

OOP の旅はまだ始まったばかりですが、継承とポリモーフィズムを掴めれば、柔軟に拡張できる設計への第一歩を踏み出せます。ぜひ自分のプロジェクトで試してみてください。

訪問数 3 回, 今日の訪問数 1回