派生クラス型・インターフェイス型・基底クラス型の違いとは?

ポリモーフィズムを引数設計から学ぼう


はじめに

C#でオブジェクトを扱うとき、次のように変数を宣言できます。

FoodiePet pet1 = new FoodiePet("エイミー");      // 具体クラス型
IVirtualPet pet2 = new FoodiePet("クー");        // インターフェイス型
VirtualPet pet3 = new FoodiePet("ライアン"); // 基底クラス型(※例として)

このように、変数の型をどこまで抽象化するかで、コードの柔軟性や設計の拡張性が大きく変わってきます。

今回は、ポリモーフィズムを理解する上で重要な「引数として渡すときの型指定」に注目し、インターフェイス型と基底クラス型の違いも含めて解説します。


1. まず押さえたい:変数の静的型の違い

型の種類説明
具体クラス型FoodiePet pet = …クラス特有の機能も使えるが、他の型と混ぜにくい
インターフェイス型IVirtualPet pet = …実装クラスを意識せず共通処理ができる
基底クラス型VirtualPetBase pet = …実装の一部を共通化しつつ、共通処理も可能

2. よくある誤解:「varを使うとポリモーフィズムが効かない?」

var pet = new FoodiePet("エイミー"); // 型は FoodiePet に推論される

これは実質次と同じです:

FoodiePet pet = new FoodiePet("エイミー");

つまり、varはただの省略記法にすぎません。ポリモーフィズムにおいて重要なのは、右辺の型ではなく、変数の「静的型」です。


3. メソッドの引数で違いが出る

次のような共通メソッドがあったとしましょう。

void InteractWithPet(IVirtualPet pet)
{
    pet.Play();
    pet.Eat();
    Console.WriteLine($"{pet.Name} の機嫌: {pet.Mood}, エネルギー: {pet.Energy}");
}

これは IVirtualPet を実装しているクラスであれば、すべて同じ処理で扱えます。つまり、ポリモーフィズムの力が引数設計に現れます。


✔ クラス特有の型(具象型)を使うと…

FoodiePet pet = new FoodiePet("エイミー");
pet.SniffFood(); // OK:FoodiePetにしかないメソッド
InteractWithPet(pet); // 暗黙的にIVirtualPetに変換されて渡される

具象型では特有の操作もできますが、他の種類と共通の操作で扱うには不向きです。


✔ インターフェイス型にすると…

IVirtualPet pet = new FoodiePet("クー");
// pet.SniffFood(); // コンパイルエラー:IVirtualPetに定義されていない

インターフェイスにない操作は封印され、「共通の使い方」だけが可能になります。


4. インターフェイスか基底クラスか:どう選ぶ?

🔷 インターフェイスを使うと…

  • クラス間の共通の契約(=必ず持つメソッド)だけを定義
  • 実装の共通部分は持てない(=すべてのクラスで書く必要あり)
  • 多重継承が可能(複数のインターフェイスを実装できる)

🔷 抽象基底クラスを使うと…

abstract class VirtualPet
{
    public string Name { get; protected set; }
    public int Mood { get; set; }
    public int Energy { get; set; }

    public abstract void Eat();
    public abstract void Play();
    public virtual void Sleep()
    {
        Energy += 2;
        Mood -= 1;
    }
}
  • 共通プロパティや実装の一部を基底クラスにまとめられる
  • ただし継承は1つだけ(他のクラスを同時に継承できない)

5. ポリモーフィズム設計の実例

List<IVirtualPet> pets = new List<IVirtualPet>
{
    new FoodiePet("エイミー"),
    new SleepyPet("ライアン"),
    new CheerfulPet("クー")
};

foreach (var pet in pets)
{
    InteractWithPet(pet);  // 見た目は同じ、動作は異なる
}

InteractWithPet の引数が IVirtualPet であることで、種類の違うペットも一貫して扱えるのです。


6. まとめ

比較項目具体クラス型インターフェイス型基底クラス型
特有メソッドの使用できるできないできない(抽象型に依存)
共通処理の利用△(制限あり)
実装の共通化××
複数継承の可否×
柔軟性・拡張性中〜高

✅ 練習課題

  1. IVirtualPet を抽象クラス VirtualPet に置き換えてみよう
  2. InteractWithPet をオーバーロードして、基底クラス版も用意してみよう
  3. 特定のペットクラスに専用機能(例:SniffFood())を加え、それが使える型と使えない型を確認しよう

以下は ✅ 練習課題 1〜3 をすべて満たす「完成形サンプル」です。

Main メソッドを含む 1 ファイルにまとめてあるので、そのまま dotnet run/Visual Studio でビルド→実行できます。

using System;
using System.Collections.Generic;

#region 1. インターフェイスを抽象基底クラスへ置き換え
// ― 共通のデータと振る舞いを持たせたいので abstract class に変更
abstract class VirtualPet
{
    public string Name   { get; protected set; }
    public int    Mood   { get; protected set; }   // 0(不機嫌)~10(ご機嫌)
    public int    Energy { get; protected set; }   // 0(疲労) ~10(元気)

    protected VirtualPet(string name)
    {
        Name   = name;
        Mood   = 5;
        Energy = 5;
    }

    public abstract void Eat();       // 各派生クラスごとに具体実装
    public abstract void Play();      // 同上

    // 全ペット共通の既定動作は virtual で用意
    public virtual void Sleep()
    {
        Energy = Math.Min(Energy + 2, 10);
        Mood   = Math.Max(Mood   - 1, 0);
        Console.WriteLine($"{Name} は眠って回復した(Energy↑ / Mood↓)");
    }
}
#endregion

#region 派生クラス
class FoodiePet  : VirtualPet
{
    public FoodiePet(string name) : base(name) { }

    public override void Eat () { Energy = Math.Min(Energy + 3, 10); }
    public override void Play() { Energy--; Mood++; }

    // 3. 専用機能(SniffFood)は FoodiePet だけが持つ
    public void SniffFood()
    {
        Console.WriteLine($"{Name} は食べ物の匂いをかいでワクワクしている!");
        Mood = Math.Min(Mood + 1, 10);
    }
}

class SleepyPet  : VirtualPet
{
    public SleepyPet(string name) : base(name) { }

    public override void Eat () { Energy += 2; }
    public override void Play() { Energy--; Mood--; }
}

class CheerfulPet : VirtualPet
{
    public CheerfulPet(string name) : base(name) { }

    public override void Eat () { Energy += 1; Mood += 2; }
    public override void Play() { Energy--; Mood += 2; }
}
#endregion

class Program
{
    // 2-A. 基底クラス版(共通ロジック)
    static void InteractWithPet(VirtualPet pet)
    {
        pet.Play();
        pet.Eat();
        Console.WriteLine(
            $"[{pet.GetType().Name}] {pet.Name} → Mood:{pet.Mood}, Energy:{pet.Energy}");
    }

    // 2-B. オーバーロード:FoodiePet 専用ロジックを追加
    static void InteractWithPet(FoodiePet pet)
    {
        pet.SniffFood();          // 派生クラス特有の機能
        InteractWithPet((VirtualPet)pet);   // 共通処理へフォールバック
    }

    static void Main()
    {
        // ★ 動作確認シナリオ
        var pets = new List<VirtualPet>
        {
            new FoodiePet ("エイミー"),   // FoodiePet
            new SleepyPet ("ライアン"),   // SleepyPet
            new CheerfulPet("クー")       // CheerfulPet
        };

        foreach (var p in pets)
        {
            // 静的型が VirtualPet なので SniffFood は見えない
            // p.SniffFood(); // ← コンパイル エラーになることを確認 ◎

            // 動的型に応じて最適なオーバーロードが選択される
            InteractWithPet(p);
            Console.WriteLine();
        }

        // 静的型を FoodiePet にすると SniffFood が呼べる
        FoodiePet foodie = new FoodiePet("ミント");
        foodie.SniffFood();           // OK
        InteractWithPet(foodie);      // FoodiePet 版 → 基底クラス版へ
    }
}

使い方と確認ポイント

課題具体的にやったこと期待できる学習効果
1IVirtualPet → VirtualPet (abstract class) に変更し、共通プロパティを集約「共通実装を基底クラスにまとめる利点」を体感
2InteractWithPet(VirtualPet pet) と InteractWithPet(FoodiePet pet) の オーバーロード を追加「静的型解決 vs 動的ディスパッチ」「メソッド選択のルール」を実践で確認
3FoodiePet.SniffFood() を独自追加し、変数の静的型違いで呼べる/呼べないを比較「型を抽象化すると専用機能は隠れる」= 設計上のトレードオフを理解

実行例(抜粋)

エイミー は食べ物の匂いをかいでワクワクしている!
[FoodiePet] エイミー → Mood:7, Energy:7

[SleepyPet] ライアン → Mood:4, Energy:6

[CheerfulPet] クー → Mood:8, Energy:5

ミント は食べ物の匂いをかいでワクワクしている!
[FoodiePet] ミント → Mood:7, Energy:7
  • InteractWithPet には 静的型 が VirtualPet の変数しか渡していないため SniffFood は呼べません。
  • FoodiePet 専用オーバーロードがあることで、実際のオブジェクトが FoodiePet のときだけ追加処理 を注入できています。

✍ まとめ

ポリモーフィズムの基本は「共通の使い方」で異なる中身を動かすこと。

そのためにインターフェイスや抽象基底クラスで型を設計することが大切です。

varを使うかどうかではなく、変数の静的型をどうするかが、設計の分かれ道です。


訪問数 10 回, 今日の訪問数 11回