ボタンひとつでわかるポリモーフィズム ― Enemy/Slime/Goblinで学ぶ

この記事は、ポリモーフィズムを最短距離で体感したい初学者向けです。まずはボタンを押すだけの最小実装で、「同じ呼び出しでも実体によって結果が変わる」を確認します(Console.WriteLine は [出力] ウィンドウに表示)。続いて、UIへのログ表示や未選択ガード、抽象化などを加えた改善版へ進み、学びをそのまま実務的な書き方に接続します。必要なのはC#の基本文法とVisual Studioの操作だけ。5〜10分で一通り体験できます。

ゴール

  • 同じ型(Enemy)に対して同じ呼び出し(TakeDamage())でも、実体により結果が変わることを体験
  • まずは最小コード → その後実用的な改善

第1章:まずは基本アイデア(最小コード)

UI(WinForms)

  • ボタン3つ:slime(スライム)/goblin(ゴブリン)/button1(こうげき)

コード(そのまま動く最小版)

using System;
using System.Windows.Forms;

namespace PolySample
{
    public partial class Form1 : Form
    {
        Enemy enemy;

        public Form1()
        {
            InitializeComponent();
        }

        private void slime_Click(object sender, EventArgs e)
        {
            enemy = new Slime();
        }

        private void goblin_Click(object sender, EventArgs e)
        {
            enemy = new Goblin();
        }

        private void button1_Click(object sender, EventArgs e) // 「こうげき」
        {
            enemy.TakeDamage();
        }
    }

    class Enemy
    {
        public virtual void TakeDamage()
        {
            Console.WriteLine("ダメージを受けた(ダミー)");
        }
    }

    class Slime : Enemy
    {
        public override void TakeDamage()
        {
            Console.WriteLine("スライムがダメージを受けた");
        }
    }

    class Goblin : Enemy
    {
        public override void TakeDamage()
        {
            Console.WriteLine("ゴブリンがダメージを受けた");
        }
    }
}

ここでの学び

  • 変数の型は Enemy だが、実体が Slime か Goblin かで、同じ TakeDamage() でも出力が変わる(ポリモーフィズム)

重要:Console.WriteLine の表示場所

WinForms ではコンソールが出ません。Visual Studio 実行中に「出力」ウィンドウに表示されます。

  • 実行(F5)→ メニュー [表示] > [出力](Ctrl+Alt+O)
  • 「出力」ウィンドウ上部のドロップダウンで 「デバッグ」 を選択
  • Console.WriteLine(…) の内容がそこで確認できます(補足:Debug.WriteLine(…) でも同様に出ます)

最小版の注意点(あえて残している課題)

  • 敵を選ばずに「こうげき」を押すと NullReferenceException になる
  • 出力が「出力ウィンドウ」なので、アプリ画面上では見えない

第2章:学びやすさ&実務寄りに改善

改善ポイント

  1. Null対策:敵未選択で攻撃できないようにする
  2. UIでログ表示:ListBox へ表示(学習者に親切)
  3. 意図の明確化:Enemy を abstract にして 子クラスへ実装を強制
  4. 命名の整理:btnSlime, btnGoblin, btnAttack, lstLog, _enemy

追加のUI

  • ListBox:lstLog
  • btnAttack.Enabled = false(起動時は攻撃ボタンを無効)

改善版コード

using System;
using System.Windows.Forms;

namespace PolySample
{
    public partial class Form1 : Form
    {
        private Enemy _enemy;

        public Form1()
        {
            InitializeComponent();
            btnAttack.Enabled = false; // 敵が決まるまで無効
        }

        private void btnSlime_Click(object sender, EventArgs e)
        {
            _enemy = new Slime();
            Log("スライムがあらわれた!");
            btnAttack.Enabled = true;
        }

        private void btnGoblin_Click(object sender, EventArgs e)
        {
            _enemy = new Goblin();
            Log("ゴブリンがあらわれた!");
            btnAttack.Enabled = true;
        }

        private void btnAttack_Click(object sender, EventArgs e)
        {
            if (_enemy == null)
            {
                Log("先に敵を選んでください。");
                return;
            }

            string result = _enemy.TakeDamage(); // ポリモーフィズム
            Log(result);
        }

        private void Log(string message) => lstLog.Items.Add(message);
    }

    // 子クラスに実装を強制して意図を明確化
    abstract class Enemy
    {
        public abstract string TakeDamage();
    }

    class Slime : Enemy
    {
        public override string TakeDamage() => "スライムがダメージを受けた(ぷるぷる)";
    }

    class Goblin : Enemy
    {
        public override string TakeDamage() => "ゴブリンがダメージを受けた(うめき声)";
    }
}

改善版で伝えられること

  • 安全性(Null防止・UI制御)
  • 拡張容易性(新しい敵クラスを追加しても、攻撃ボタン側は修正不要)
  • 責務分離の感覚(画面は「呼ぶだけ」、ふるまいは各クラスが自分で持つ

仕上げ:発展ミニ演習

  • Dragon : Enemy を追加し、ボタン1つ加えるだけで動くことを確認
  • Defend() を Enemy に追加し、各クラスで実装 → 「ぼうぎょ」ボタンから呼び出す

この構成(最小版→改善版)で記事化すれば、初学者が「まず動いた!」を体験しつつ、その直後に「なぜ改善が必要か/どう良くなるか」まで自然に理解できます。必要なら、このままブログ本文用に整形(見出し・前後文のつなぎ・画像キャプション)まで仕上げます。

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