CodeGym /コース /C# SELF /ポリモーフィズムの実践的な使い方

ポリモーフィズムの実践的な使い方

C# SELF
レベル 21 , レッスン 3
使用可能

1. ポリモーフィズムの実践

プログラミングでのポリモーフィズムって、まるでユニバーサルリモコンみたいなもんだよ。"音量+"ボタンを押すと、テレビだったりオーディオだったりエアコンだったり、それぞれ違う反応をするけど、インターフェースは一緒!同じように、いろんな型のオブジェクトが、共通の親クラスでvirtualとして定義されたメソッドに対して、それぞれ違う反応を返せるんだ。

C#では、基底クラス(またはインターフェース)型の変数が、その子孫のどんなオブジェクトでも「持てる」し、その変数でvirtualメソッドを呼ぶと、実際のオブジェクトで定義された「本物」の実装が実行される。これが、ロジックが動的に変わるアーキテクチャの基礎になってるんだよ。

これって教科書以外で使うの?

もちろん!動物園の動物からGUIの部品、イベントハンドラやドキュメント管理システムまで、似てるけど違うオブジェクトを扱うプロジェクトなら、ほぼどこでも使うよ。

  • 汎用アルゴリズムを作れる ― 具体的な実装の細かいところを気にせず、抽象的なものとしてコードを書ける。
  • 拡張性バツグン ― 「動物」「図形」「ハンドラ」など、いくらでも新しい種類を追加できるし、既存のコードはそのままでOK。
  • プログラムのコンポーネント同士の依存を減らせる(これ、面接や設計でめっちゃ大事な話!)。

2. 基本の構文と仕組み

じゃあ、AnimalDogCatクラスを思い出して、ポリモーフィズムを実際に見てみよう。「バーチャル動物園」アプリをもっと進化させる感じでやってみるね。

virtualメソッドを持つ基底クラス


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

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

    // バーチャルメソッド ― オーバーライドできる
    public virtual void MakeSound()
    {
        Console.WriteLine($"{Name} は何か音を出す…");
    }
}
        
public class Dog : Animal
{
    public Dog(string name) : base(name) { }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} は言う: ワンワン!");
    }
}
public class Cat : Animal
{
    public Cat(string name) : base(name) { }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} は言う: ニャー!");
    }
}

ポリモーフィズムの使い方:動物コレクションの例

例えば、いろんな動物(ペットもそうじゃないのも)をリストにして、みんなに「鳴いて」もらいたいとする。ポリモーフィズムがなかったら型チェックやら重複コードだらけ。でもポリモーフィズムなら超スマート!


// いろんな種類の動物の配列を作る
Animal[] animals = new Animal[]
{
    new Dog("ボビク"),
    new Cat("ムルカ"),
    new Dog("シャリク"),
    new Cat("バルシク"),
};

// 配列を全部回って、それぞれに鳴いてもらう
foreach (var animal in animals)
{
    animal.MakeSound(); // DogやCatのメソッドが呼ばれるよ、Animalじゃなくて!
}
        

結果:

ボビク は言う: ワンワン!
ムルカ は言う: ニャー!
シャリク は言う: ワンワン!
バルシク は言う: ニャー!

これが魔法みたいなとこ:同じコードで、オブジェクトの実際の型によって違う結果が出るんだ。

図解:ポリモーフィズムの仕組み


Animal (基底クラス)
   /           \
Dog           Cat
        
クラスの階層: Animal → Dog, Cat

animal.MakeSound()を呼ぶとき、AnimalDogCatのインスタンスを持ってても、.NETランタイムが実行時にどのMakeSound()を呼ぶか自動で判断してくれるよ。

3. ポリモーフィズムでよくある課題の解決

例1: 汎用リストでいろんな動作

例えばゲームを作ってるとしよう。基底クラスGameObjectがあって、派生クラスは敵、味方、障害物とか。みんな動くし、Update()メソッドがあるけど、中身はそれぞれ違う。

public class GameObject
{
    public virtual void Update() { }
}

public class Enemy : GameObject
{
    public override void Update()
    {
        Console.WriteLine("敵が攻めてくる!");
    }
}

public class Friend : GameObject
{
    public override void Update()
    {
        Console.WriteLine("味方が助けてくれる!");
    }
}

GameObject[] objects = new GameObject[]
{
    new Enemy(),
    new Friend(),
    new Enemy()
};

foreach (var obj in objects)
{
    obj.Update();
}
// 出力:
// 敵が攻めてくる!
// 味方が助けてくれる!
// 敵が攻めてくる!

例2: オブジェクトをメソッドに渡す

基底型のパラメータを受け取って、どんな派生型でも使える。新しい子孫クラスが増えても、めっちゃ楽できるよ。

public static void FeedAnimal(Animal animal)
{
    Console.Write($"{animal.Name}: ");
    animal.MakeSound();
    Console.WriteLine("そしてごはんをもらう。");
}

FeedAnimal(new Dog("レックス"));
FeedAnimal(new Cat("シマ"));

// 結果:
// レックス: レックス は言う: ワンワン!
// そしてごはんをもらう。
// シマ: シマ は言う: ニャー!
// そしてごはんをもらう。

ポリモーフィズムなしでこういうメソッドを書くと、型チェックや分岐だらけで大変だよ。

重要ポイント: 実行時バインディング

ポリモーフィズムは、virtualメソッドの呼び出しが動的、つまりプログラムの実行時に決まることで成り立ってる。これを遅延バインディング(late binding)って呼ぶよ。変数がAnimal型でも、実際のオブジェクトで定義されたメソッドが呼ばれるんだ。

逆に、メソッドがvirtualじゃなかったら、変数の型で決まったメソッドしか呼ばれない。柔軟性が欲しいならvirtualはケチらず使おう!

4. 実践:アプリを拡張してみよう

例えば、バーチャル動物園に新機能を追加したいとする。今度は、すべての動物がMakeSound()だけじゃなくて、Move()(動く)もやることに。でも、みんな動き方は違うよね。

1. 基底クラスにvirtualメソッドを追加


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

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

    public virtual void MakeSound()
    {
        Console.WriteLine($"{Name} は何か音を出す…");
    }

    public virtual void Move()
    {
        Console.WriteLine($"{Name} は不明な方法で動く…");
    }
}
        

2. 派生クラスでMove()をそれぞれ実装

public class Dog : Animal
{
    public Dog(string name) : base(name) { }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} は言う: ワンワン!");
    }

    public override void Move()
    {
        Console.WriteLine($"{Name} は棒を追いかけて走る。");
    }
}

public class Cat : Animal
{
    public Cat(string name) : base(name) { }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} は言う: ニャー!");
    }

    public override void Move()
    {
        Console.WriteLine($"{Name} はやわらかい足でこっそり歩く。");
    }
}

3. コレクションで両方のメソッドを使う

Animal[] animals = new Animal[]
{
    new Dog("ビム"),
    new Cat("リュシア")
};

foreach (var animal in animals)
{
    animal.MakeSound();
    animal.Move();
}

結果:

ビム は言う: ワンワン!
ビム は棒を追いかけて走る。
リュシア は言う: ニャー!
リュシア はやわらかい足でこっそり歩く。

実際のアプリでも、こういうやり方でめっちゃ強力で再利用しやすいモジュールが作れる。ゲームならNPCからエフェクトまで、オブジェクト管理の基本だよ。

5. 実践課題:いろんな図形を描く

今度は動物園じゃなくてグラフィックの例。基底クラスShapeにvirtualなDraw()メソッドを作って、そこから拡張してみよう。

public class Shape
{
    public virtual void Draw()
    {
        Console.WriteLine("未定義の図形を描画する。");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("円を描画する。");
    }
}

public class Rectangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("長方形を描画する。");
    }
}

// 図形のコレクション
Shape[] shapes = new Shape[]
{
    new Circle(),
    new Rectangle(),
    new Circle()
};

foreach (var shape in shapes)
{
    shape.Draw();
}

ここでDraw()Shape型の変数で呼ばれてるけど、実際にはCircleRectangleのメソッドが呼ばれる。WinFormsやWPFみたいな本物のグラフィックライブラリでも、まさにこうなってるよ。

6. 一般的なやり方:汎用アルゴリズムを書く

ポリモーフィズムを使うと、コードがめっちゃ柔軟で拡張しやすくなる。コレクションにDogやCatだけじゃなくて、HamsterやParrotとか何でも追加できるし、共通のやり方で全部処理できる。

さらに、基底型を受け取るメソッドに派生オブジェクトを渡すのも余裕だよ:

void PrintAnimalInfo(Animal animal)
{
    Console.WriteLine($"名前: {animal.Name}");
    animal.MakeSound();
    animal.Move();
}

Animal hamster = new Animal("ホマ");
Animal dog = new Dog("ロード");
PrintAnimalInfo(hamster); // Animalのメソッドを使う
PrintAnimalInfo(dog);     // Dogバージョンを使う

7. 便利なポイント

よくある質問と落とし穴

初心者がよくやる勘違いは、Dogオブジェクトを作ってDog myDogで宣言しても、Animal myDogで宣言しても同じだと思うこと。実は、Animal型で宣言すると、Animalにあるものしか「見えない」(オーバーライドされたメソッド以外)、Dog型で宣言すればBark()や独自プロパティも全部見えるよ。

それと、基底クラスのメソッドがvirtualじゃないと、オーバーライドできない。もし派生クラスでoverrideしようとしたら、コンパイラがエラー出すよ。

ちなみに、virtualじゃないメソッドを本当に置き換えたいならnewキーワードを使うけど、これは上級(そしてややこしい!)話題だね。

なんで面接でよく聞かれるの?

ポリモーフィズムはプログラマーのスイスアーミーナイフみたいなもん。これを理解して使いこなせれば、拡張性と保守性の高いシステムが作れるし、コードも長生きする。例えば、いろんな支払い方法(定番:BankCard, PayPal, Bitcoin)の処理を実装してって言われたら、共通インターフェース(または抽象基底クラス)にPay()メソッドを作って、Pay(BankCard)Pay(PayPal)Pay(Bitcoin)みたいにクライアントが中身を気にせず使えるようにするのが期待されてるんだ。

8. 初心者がやりがちなミス

よくあるミスの一つは、基底型の変数で派生クラスの専用メソッドを呼ぼうとすること。例えば:


Animal animal = new Dog("トゥジク");
animal.Bark(); // エラー!AnimalにはBarkメソッドがない。
        

なんで動かないの?それは、Animal型の変数は、Animalで宣言されたものしか「見えない」から。実際はDogでも、呼べるのは基底クラスで定義されてて(overrideされてる)ものだけ。

どうしてもDog専用メソッドを呼びたいなら、型変換が必要:

Animal animal = new Dog("トゥジク");
if (animal is Dog dog)
{
    dog.Bark();
}

でも、こういうことをよくやるなら、設計がどこかおかしい(もしくは継承を乱用してる)かもね。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION