ボタンひとつでわかるポリモーフィズム ― 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章:学びやすさ&実務寄りに改善
改善ポイント
- Null対策:敵未選択で攻撃できないようにする
- UIでログ表示:ListBox へ表示(学習者に親切)
- 意図の明確化:Enemy を abstract にして 子クラスへ実装を強制
- 命名の整理: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回
ディスカッション
コメント一覧
まだ、コメントがありません