基礎から学ぶC#入門 90日コース | 継承・抽象化・インターフェース - Day 45:多態性

C# 90日で身につけるC#
スポンサーリンク
スポンサーリンク

Day 45 のゴールと全体像

Day 45 のテーマは 多態性(ポリモーフィズム)を「実践する」ことです。

キーワードはとてもシンプルです。

同じメソッド ↓ 異なる動作

これを、実際のコードで体験していきます。

ここまでで、

  • 継承
  • virtual / override
  • 抽象クラス
  • インターフェース

を学んできましたが、 それらが「多態性」というひとつのゴールに向かっていた、 という流れを感じてもらえると嬉しいです。

多態性とは何かをもう一度整理する

一言でいうと「同じ呼び方で、違う動き」

多態性(ポリモーフィズム)は、

「同じメソッド名で呼んでいるのに、 実際のオブジェクトの種類によって動きが変わること」

です。

たとえば、

  • animal.Speak() と呼ぶと、
    • 犬なら「ワン!」
    • 猫なら「ニャー!」
    • 鳥なら「チュンチュン」

というふうに、 同じ Speak() なのに、動きが違う

これが多態性です。

ステップ1:基底クラス+virtual で「共通の窓口」を作る

動物クラス Animal を用意する

まずは、共通の「窓口」となる基底クラスを作ります。

using System;

// 基底クラス:動物
class Animal
{
    public string Name { get; set; }

    public Animal(string name)
    {
        Name = name;
    }

    // 仮想メソッド:鳴く
    // 「動物なら必ず鳴く」という共通の窓口
    public virtual void Speak()
    {
        Console.WriteLine($"{Name} は何か鳴きます。");
    }
}
C#

ここでのポイントは、

  • public virtual void Speak() → 「動物は鳴く」という 共通のメソッド名 を用意している
  • 中身は「とりあえずのデフォルト実装」

というところです。

ステップ2:派生クラス+override で「動きを変える」

犬クラス Dog

// 犬クラス:Animal を継承
class Dog : Animal
{
    public Dog(string name) : base(name)
    {
    }

    // Speak を犬用にオーバーライド
    public override void Speak()
    {
        Console.WriteLine($"{Name} は『ワン!』と鳴きます。");
    }
}
C#

猫クラス Cat

// 猫クラス:Animal を継承
class Cat : Animal
{
    public Cat(string name) : base(name)
    {
    }

    // Speak を猫用にオーバーライド
    public override void Speak()
    {
        Console.WriteLine($"{Name} は『ニャー!』と鳴きます。");
    }
}
C#

鳥クラス Bird

// 鳥クラス:Animal を継承
class Bird : Animal
{
    public Bird(string name) : base(name)
    {
    }

    // Speak を鳥用にオーバーライド
    public override void Speak()
    {
        Console.WriteLine($"{Name} は『チュンチュン』と鳴きます。");
    }
}
C#

ここでの重要ポイントは、

  • どのクラスもメソッド名は Speak のまま
  • 中身だけがクラスごとに違う

というところです。

ステップ3:同じメソッド呼び出しで異なる動作を体験する

Animal 型でまとめて扱う

多態性を一番実感できるのは、 「同じ型として扱っているのに、動きが違う」瞬間です。

using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        // Animal 型のリストに、Dog / Cat / Bird をまとめて入れる
        List<Animal> animals = new List<Animal>
        {
            new Dog("ポチ"),
            new Cat("タマ"),
            new Bird("ピーちゃん")
        };

        Console.WriteLine("=== 動物たちが一斉に鳴きます ===");

        foreach (var animal in animals)
        {
            // 型は Animal、呼び出すメソッドは常に Speak
            // でも、実際の動きはクラスごとに違う
            animal.Speak();
        }

        Console.ReadLine();
    }
}
C#

実行すると、

  • ポチ は『ワン!』
  • タマ は『ニャー!』
  • ピーちゃん は『チュンチュン』

というふうに、 同じ animal.Speak() なのに、動きが変わります。

これが、Day 45 のテーマである

同じメソッド ↓ 異なる動作

の、まさに実例です。

ステップ4:抽象クラス+抽象メソッドで「仕様を強制する」

抽象クラス版 Animal

もう少し設計寄りにすると、 「動物なら必ず Speak() を持っていてほしい」という仕様を 抽象クラスで表現できます。

// 抽象クラス:動物の共通仕様
abstract class AbstractAnimal
{
    public string Name { get; set; }

    public AbstractAnimal(string name)
    {
        Name = name;
    }

    // 抽象メソッド:必ず実装しなければならない
    public abstract void Speak();

    // 共通処理
    public void Introduce()
    {
        Console.WriteLine($"私は {Name} です。");
    }
}
C#

具体クラス Dog / Cat / Bird

class AbstractDog : AbstractAnimal
{
    public AbstractDog(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} は『ワン!』と鳴きます。");
    }
}

class AbstractCat : AbstractAnimal
{
    public AbstractCat(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} は『ニャー!』と鳴きます。");
    }
}

class AbstractBird : AbstractAnimal
{
    public AbstractBird(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} は『チュンチュン』と鳴きます。");
    }
}
C#

使う側はこうです。

class Program
{
    static void Main(string[] args)
    {
        List<AbstractAnimal> animals = new List<AbstractAnimal>
        {
            new AbstractDog("ポチ"),
            new AbstractCat("タマ"),
            new AbstractBird("ピーちゃん")
        };

        foreach (var animal in animals)
        {
            animal.Introduce(); // 共通処理
            animal.Speak();     // クラスごとに違う動き
        }

        Console.ReadLine();
    }
}
C#

ここでも、

  • Introduce は共通処理
  • Speak は「同じメソッド名で異なる動作」

という構造になっています。

ステップ5:インターフェースで「役割ごとの多態性」を作る

IShape インターフェース

今度は「図形」を例にして、 インターフェースで多態性を実践してみます。

// 図形のインターフェース:面積を計算できるという契約
interface IShape
{
    double GetArea();
}
C#

円と四角形の実装

using System;

// 円
class Circle : IShape
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public double GetArea()
    {
        // 円の面積 = πr^2
        return Math.PI * Radius * Radius;
    }
}

// 四角形
class Rectangle : IShape
{
    public double Width { get; }
    public double Height { get; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public double GetArea()
    {
        // 四角形の面積 = 幅 × 高さ
        return Width * Height;
    }
}
C#

同じメソッドで異なる動作を実践する

using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        List<IShape> shapes = new List<IShape>
        {
            new Circle(5.0),
            new Rectangle(4.0, 3.0),
            new Circle(2.5)
        };

        Console.WriteLine("=== 図形の面積を計算します ===");

        foreach (var shape in shapes)
        {
            // 型は IShape、呼び出すメソッドは常に GetArea
            // でも、実際の計算式はクラスごとに違う
            double area = shape.GetArea();
            Console.WriteLine($"面積: {area:F2}");
        }

        Console.ReadLine();
    }
}
C#

ここでも、

  • 呼び出しているメソッドは常に GetArea()
  • 実際の計算式は、円と四角形で違う

という「同じメソッド → 異なる動作」が実現されています。

多態性がもたらすメリットを整理する

1. 呼び出し側のコードがシンプルになる

  • animal.Speak()
  • shape.GetArea()
  • task.Execute()

など、 「何をしてほしいか」だけを書けばよくなります。

実際にどう動くかは、 クラスごとの実装に任せられます。

2. 新しい種類を追加しても、既存コードをほとんど変えなくてよい

  • 新しい動物 Cow を追加しても、 List<Animal>foreach (animal.Speak()) はそのまま使えます。
  • 新しい図形 Triangle を追加しても、 List<IShape>shape.GetArea() はそのままです。

これは、アプリが大きくなったときに、 保守性・拡張性を劇的に高めてくれる考え方です。

3. 「役割」でコードを考えやすくなる

  • 「鳴くことができるもの」
  • 「面積を計算できるもの」
  • 「実行できるタスク」

といった 「役割」 を、 抽象クラスやインターフェースで表現し、 多態性でまとめて扱うことができます。

多態性の基本テンプレート

最後に、 「同じメソッド → 異なる動作」を実現するための 基本テンプレートをまとめておきます。

抽象クラス版

// 抽象クラス:共通仕様
abstract class BaseAction
{
    public string Name { get; }

    protected BaseAction(string name)
    {
        Name = name;
    }

    // 抽象メソッド:必ず実装する
    public abstract void Execute();
}
C#
// 具体クラスA
class EmailAction : BaseAction
{
    public EmailAction() : base("メール送信") { }

    public override void Execute()
    {
        Console.WriteLine("メールを送信しています...");
    }
}

// 具体クラスB
class ReportAction : BaseAction
{
    public ReportAction() : base("レポート生成") { }

    public override void Execute()
    {
        Console.WriteLine("レポートを生成しています...");
    }
}
C#
// 使う側
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        List<BaseAction> actions = new List<BaseAction>
        {
            new EmailAction(),
            new ReportAction()
        };

        foreach (var action in actions)
        {
            Console.WriteLine($"アクション: {action.Name}");
            action.Execute(); // 同じメソッド名で異なる動作
        }

        Console.ReadLine();
    }
}
C#

Day 45 のまとめ

Day 45 では、

  • 同じメソッド名で呼んでいるのに、クラスごとに違う動きをする という「多態性」を、 動物・図形・タスクの例で実践しました。

キーワードはシンプルです。

同じメソッド ↓ 異なる動作

その裏側には、

  • 継承
  • virtual / override
  • 抽象クラス
  • インターフェース

といった仕組みが、 きれいに組み合わさっています。

ぜひ、

  • 自分で「共通のメソッド名」を決めて
  • それを持つクラスを複数作り
  • リストや配列にまとめて、同じメソッドを呼んでみる

という小さな練習を通して、 多態性を 「教科書の言葉」ではなく、 自分のコードの中で生きている感覚として、 少しずつ育てていってください。

タイトルとURLをコピーしました