Day 43 のゴールと全体像
Day 43 のテーマは 抽象クラスです。 キーワードは次の3つになります。
abstract- 抽象メソッド
- 共通仕様
継承や virtual/override を学んできた流れの中で、 「もっと設計寄りの話」に一歩踏み込む回だと思って読んでいただけるとよいと思います。
抽象クラスとは何か
「共通仕様だけを決めて、具体的な中身は子クラスに任せるクラス」
抽象クラスは、
「この種類のクラスは、こういうメソッドを必ず持っていてほしい」
という “仕様” を表現するためのクラスです。
特徴をざっくりまとめると、
abstractキーワードを付けて宣言する- インスタンスを直接作ることはできない
- 共通のフィールド・プロパティ・メソッドを持てる
- 「中身は書かず、形だけ決めるメソッド(抽象メソッド)」を持てる
という感じです。
まずは普通の基底クラスとの違いをイメージする
基底クラス(通常のクラス)の例
前回までの復習として、普通の基底クラスを見てみます。
using System;
// 通常の基底クラス
class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
// 仮想メソッド:鳴く
public virtual void Speak()
{
Console.WriteLine($"{Name} は何か鳴きます。");
}
}
C#この Animal は、
- そのまま
new Animal("名前")でインスタンスを作れる Speak()の中身も書かれている
という「普通のクラス」です。
抽象クラスのイメージ
一方、抽象クラスはこうです。
「
Animalという種類のクラスは、必ずSpeak()というメソッドを持っていてほしい。 でも、どう鳴くかは動物ごとに違うから、子クラスで決めてね。」
という “ルールだけ決めるクラス” になります。
abstract キーワードで抽象クラスを作る
抽象クラス Animal の例
using System;
// 抽象クラス:インスタンスを直接作れない「設計図的なクラス」
abstract class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
// 抽象メソッド:中身を書かず、「必ず実装しなさい」という形だけ決める
public abstract void Speak();
// 共通処理を持つこともできる
public void Introduce()
{
Console.WriteLine($"私は {Name} です。");
}
}
C#ここでのポイントは、
abstract class Animal→ 抽象クラスの宣言。直接newできません。public abstract void Speak();→ 抽象メソッド。中身(本体)がありません。- 抽象クラスでも、普通のメソッド(
Introduce)は持てる
というところです。
抽象メソッドとは何か
「必ずオーバーライドしなければならないメソッド」
抽象メソッドは、
「このクラスを継承するなら、このメソッドは必ず実装してください」
という “強制力のある仕様” を表現するためのものです。
- 基底クラス側では「形だけ」決める
- 派生クラス側で必ず
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)
{
}
public override void Speak()
{
Console.WriteLine($"{Name} は『ニャー!』と鳴きます。");
}
}
C#ここでの重要ポイントは、
- 抽象メソッド
Speakを持つ抽象クラスAnimalを継承したら、 派生クラス側で必ずoverrideしなければならない - もし
Speakを実装しないと、コンパイルエラーになります
つまり、抽象クラスは、
「この種類のクラスは、必ずこのメソッドを持っているべきだ」
という 共通仕様をコードで表現するための仕組みなのです。
抽象クラスを使う側のコード
多態性+抽象クラスの組み合わせ
抽象クラスは、 前回の多態性(ポリモーフィズム)と組み合わせると、 とても強力になります。
class Program
{
static void Main(string[] args)
{
// 抽象クラス Animal 型の変数に、具体クラス Dog と Cat を入れる
Animal a1 = new Dog("ポチ");
Animal a2 = new Cat("タマ");
// 共通処理(Introduce)は抽象クラス側に書いてある
a1.Introduce();
a2.Introduce();
// 抽象メソッド Speak は、各クラスごとに違う動きをする
a1.Speak(); // ポチ は『ワン!』
a2.Speak(); // タマ は『ニャー!』
Console.ReadLine();
}
}
C#ここでの流れはこうです。
Animalは「動物という概念」を表す抽象クラスDogとCatは「具体的な動物」を表すクラスIntroduceは共通処理として抽象クラスに書いてあるSpeakは「動物なら必ず持つべきメソッド」として抽象メソッドで宣言され、 各クラスが自分流に実装している
重要ポイント:
- 抽象クラスは「共通仕様+共通処理」をまとめる場所
- 具体クラスは「仕様を守りつつ、自分固有の振る舞いを実装する場所」
という役割分担になります。
抽象クラスがうれしい場面
1. 「必ずこのメソッドを持っていてほしい」を保証したいとき
例えば、 「すべての画面は Render() メソッドを持っていてほしい」 というルールを作りたいとします。
// 抽象クラス:画面の共通仕様
abstract class Screen
{
public string Title { get; set; }
public Screen(string title)
{
Title = title;
}
// すべての画面は Render を必ず実装しなければならない
public abstract void Render();
// 共通処理:タイトルを表示する
public void ShowTitle()
{
Console.WriteLine($"=== {Title} ===");
}
}
C#これを継承して、 メニュー画面や設定画面を作ります。
// メニュー画面
class MenuScreen : Screen
{
public MenuScreen() : base("メニュー")
{
}
public override void Render()
{
ShowTitle();
Console.WriteLine("1: 開始");
Console.WriteLine("2: 設定");
Console.WriteLine("0: 終了");
}
}
// 設定画面
class SettingsScreen : Screen
{
public SettingsScreen() : base("設定")
{
}
public override void Render()
{
ShowTitle();
Console.WriteLine("音量: 50");
Console.WriteLine("明るさ: 70");
}
}
C#こうしておくと、
Screenを継承したクラスは、必ずRender()を持つ- 「Render を書き忘れた画面」が存在しないことを、コンパイラが保証してくれる
という状態になります。
2. 「共通仕様+共通処理」をセットで提供したいとき
抽象クラスは、
- 抽象メソッドで「仕様」を決め
- 通常メソッドで「共通処理」を提供する
ということができるので、
「この種類のクラスは、こういうメソッドを必ず持っていて、 こういう共通処理も使えるようになっていてほしい」
という 設計の意図をコードで表現するのに向いています。
抽象クラスの基本テンプレート
最後に、抽象クラスの基本形をテンプレートとしてまとめます。
抽象クラス
// 抽象クラス:共通仕様と共通処理をまとめる
abstract class BaseTask
{
public string Name { get; set; }
public BaseTask(string name)
{
Name = name;
}
// 抽象メソッド:すべてのタスクは Execute を必ず実装する
public abstract void Execute();
// 共通処理:タスク開始メッセージ
public void Start()
{
Console.WriteLine($"タスク「{Name}」を開始します。");
}
// 共通処理:タスク終了メッセージ
public void Finish()
{
Console.WriteLine($"タスク「{Name}」が完了しました。");
}
}
C#具体タスクA
class DownloadTask : BaseTask
{
public DownloadTask(string name) : base(name)
{
}
public override void Execute()
{
Start();
Console.WriteLine("ファイルをダウンロードしています...");
Finish();
}
}
C#具体タスクB
class UploadTask : BaseTask
{
public UploadTask(string name) : base(name)
{
}
public override void Execute()
{
Start();
Console.WriteLine("ファイルをアップロードしています...");
Finish();
}
}
C#使う側
class Program
{
static void Main(string[] args)
{
BaseTask t1 = new DownloadTask("レポートのダウンロード");
BaseTask t2 = new UploadTask("画像のアップロード");
t1.Execute();
t2.Execute();
Console.ReadLine();
}
}
C#このテンプレートをベースに、
- 抽象クラスで「共通仕様+共通処理」を決める
- 具体クラスで「中身」を実装する
という形で、自分なりの抽象クラスを作ってみると、 abstract の感覚がぐっと身近になります。
Day 43 のまとめ
Day 43 では、
abstractクラス → インスタンスを直接作れない「設計図的なクラス」- 抽象メソッド → 中身を書かず、「必ず実装しなさい」という形だけ決めるメソッド
- 共通仕様 → 「この種類のクラスは、こういうメソッドを必ず持っているべきだ」というルールをコードで表現すること
を、動物や画面、タスクの例を通して学びました。
抽象クラスは、
「共通仕様をきちんと決めて、 それを守る具体クラスを増やしていく」
という、設計寄りの考え方を支える重要な仕組みです。
ぜひ、
- 自分で抽象クラスを1つ作り
- それを継承した具体クラスを2〜3個作って
- 抽象メソッドと共通処理を組み合わせてみる
という小さな練習を通して、 抽象クラスを 「難しい言葉」ではなく、 設計をきれいにするための頼れる道具として、少しずつ体に馴染ませていってください。
