1. はじめに
階層がない世界を想像してみてよ:Person、Animal、Vehicleみたいなクラスが何千個もあって、全部バラバラ。そんな世界じゃプログラマーは昼飯前に迷子になって詰むよね!実際のプロジェクトでは、共通のことができるオブジェクト(例えば、動物はみんな動ける)も必要だけど、それぞれの特徴(魚は泳ぐ、鳥は飛ぶ)も大事。
クラスの階層構造があるからこそ、エンティティ同士の関係を表現できて、プログラミングがコピペ地獄じゃなくてクリエイティブな作業になるんだ。
例を見てみよう。例えば、ベースクラスAnimalがあるとする。動物はみんな音を出せる。でも猫はニャー、犬はワン、オウムはジョークまで言えるかも。これをコードで階層的に表現したいよね。
ベースクラス
public class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
// ベースメソッド:子クラスでオーバーライドできる
public virtual void Speak()
{
Console.WriteLine("動物が何か音を出してる…");
}
}
ここでSpeak()メソッドにvirtualって付けてる。これは「ねぇ子クラス、必要ならこのメソッドをオーバーライドしていいよ」っていう印だよ。
階層を作る:派生クラス
じゃあCatクラスを作って、Animalから継承しよう:
public class Cat : Animal
{
public Cat(string name) : base(name) { }
// Speakをオーバーライド — 猫はただ唸るだけじゃない!
public override void Speak()
{
Console.WriteLine($"{Name} は言う: ニャー!");
}
}
そしてDogクラス:
public class Dog : Animal
{
public Dog(string name) : base(name) { }
public override void Speak()
{
Console.WriteLine($"{Name} は言う: ワン!");
}
}
もし普通の動物で、喋れないやつがいたら?その場合はベースクラスをそのまま使えばOK。
ビジュアライズ — 階層ツリー
. Animal
/ \
Cat Dog
- Animal — ベースクラス
- Cat, Dog — 子クラス(派生クラス)
2. こんな階層を使うコードを書いてみよう
コンソールアプリの続きをやってみよう。
例えば動物のコレクションがあって、それぞれに特徴的なことを言わせたいとする:
Animal[] zoo = new Animal[]
{
new Cat("バルシク"),
new Dog("レクス"),
new Animal("謎の生き物")
};
foreach (Animal animal in zoo)
{
animal.Speak();
}
期待される出力:
バルシク は言う: ニャー!
レクス は言う: ワン!
動物が何か音を出してる…
こんな感じで、階層とポリモーフィズム(これは後で詳しくやるけど、要はオブジェクトの実際の型に応じて正しいメソッドが呼ばれるってこと)のおかげで、アプリが柔軟で拡張しやすくなるんだ。
3. 新しいメソッドやフィールドを追加しよう
「声を出す」だけじゃつまらないよね。例えば猫には9つの命があるし、犬は棒を持ってくることができる。
ユニークな動きを追加
子クラスには独自のメソッドやフィールドを追加できるよ:
public class Cat : Animal
{
public int Lives { get; private set; } = 9;
public Cat(string name) : base(name) { }
public override void Speak()
{
Console.WriteLine($"{Name} は言う: ニャー! 私は {Lives} の命がある。");
}
public void LoseLife()
{
if (Lives > 0)
{
Lives--;
Console.WriteLine($"{Name} は命を一つ失った。残り: {Lives}");
}
else
{
Console.WriteLine($"{Name} はもう全部の命を使い切ったよ!");
}
}
}
コードで使ってみる:
var barsik = new Cat("バルシク");
barsik.Speak(); // バルシク は言う: ニャー! 私は 9 の命がある。
barsik.LoseLife(); // バルシク は命を一つ失った。残り: 8
新しいクラスを追加して「動物園」を拡張
もう派生クラスの作り方はわかったよね。例えばオウムを追加してみよう:
public class Parrot : Animal
{
public Parrot(string name) : base(name) { }
public override void Speak()
{
Console.WriteLine($"{Name} は言う: やあ、人間!");
}
public void Repeat(string phrase)
{
Console.WriteLine($"{Name} は繰り返す: {phrase}");
}
}
これで古いコードを触らずにシステムを簡単に拡張できる:
var keshka = new Parrot("ケシャ");
keshka.Speak(); // ケシャ は言う: やあ、人間!
keshka.Repeat("勉強しろ、学生!"); // ケシャ は繰り返す: 勉強しろ、学生!
4. 動物たちの動きの比較
| タイプ | Speak()メソッド | 独自フィールド | 追加の動き |
|---|---|---|---|
| Animal | あり(virtual) | Name | — |
| Cat | あり(override) | Lives | LoseLife() |
| Dog | あり(override) | — | — |
| Parrot | あり(override) | — | Repeat(string) |
クラス階層がメモリ上でどう見えるか(ブロック図)
Animal (Name)
├── Cat (Lives)
├── Dog
└── Parrot (Repeat)
5. アプリで実践してみよう
階層のアイデアをアプリに結びつけてみよう。例えば、いろんなタイプのタスクがあるとする:
- Task(ベースクラス):どんなタスクにもタイトルと完了ステータスがある。
- WorkTask(仕事用):さらに締め切りがある。
- HomeTask(家用):優先度(「超重要」「まあまあ」)がある。
まずはベースクラスから:
public class Task
{
public string Title { get; set; }
public bool IsCompleted { get; private set; }
public Task(string title)
{
Title = title;
}
public virtual void Complete()
{
IsCompleted = true;
Console.WriteLine($"タスク「{Title}」が完了した!");
}
}
次に仕事用タスクを追加:
public class WorkTask : Task
{
public DateTime Deadline { get; set; }
public WorkTask(string title, DateTime deadline)
: base(title)
{
Deadline = deadline;
}
public override void Complete()
{
base.Complete();
Console.WriteLine($"締め切り: {Deadline:d}");
}
}
そして家用タスク:
public class HomeTask : Task
{
public string Priority { get; set; }
public HomeTask(string title, string priority)
: base(title)
{
Priority = priority;
}
// ベースクラスのCompleteで十分ならオーバーライドしなくてもOK
}
プログラムでタスクリストを作ろう:
List<Task> tasks = new List<Task>
{
new WorkTask("レポートを送る", DateTime.Today.AddDays(2)),
new HomeTask("皿を洗う", "超重要"),
new Task("継承についての講義を読む")
};
foreach (Task task in tasks)
{
Console.WriteLine($"タスク: {task.Title}");
task.Complete();
}
期待される出力:
タスク: レポートを送る
タスク「レポートを送る」が完了した!
締め切り: 2025/07/13
タスク: 皿を洗う
タスク「皿を洗う」が完了した!
タスク: 継承についての講義を読む
タスク「継承についての講義を読む」が完了した!
どう?全部のタスクをまとめて管理できて、処理も一括、でも必要なところだけ個別の動きが出せるから超便利だよね。
6. 継承を使うときのよくあるミス
ミス1: virtualで宣言されてないメソッドをオーバーライドしようとする。
ベースクラスでメソッドにvirtualが付いてないと、派生クラスでオーバーライドできない。これだとポリモーフィズムの柔軟性がなくなって、階層の意味がなくなるよ。
ミス2: 意味的なつながりがないのに継承しちゃう。
オブジェクト同士に意味的な関係がないなら、継承はやめとこう。例えば円は図形だけど、馬が乗り物ってのはちょっと無理がある。例外は中世のゲームみたいな特殊な文脈だけ。
ミス3: 階層が深すぎる。
クラス構造が5~6階層以上になると、コードが読みにくくてメンテもテストも大変。そんな時はコンポジション(合成)を使うのもアリだよ。
ミス4: ベースコンストラクタの呼び出し忘れ。
派生クラスで新しいプロパティを追加するとき、base(...)をコンストラクタで明示的に呼ぶのを忘れがち。これを忘れるとベース部分の初期化が不完全になって、バグの元になるよ。
GO TO FULL VERSION