Day 44 のゴールと全体像
Day 44 のテーマは インターフェース(interface)です。 キーワードはこの3つになります。
interface- 契約
- 実装
抽象クラスまで来たところで、 「もうひとつの共通仕様の表し方」としてインターフェースを学ぶ回です。
ざっくり言うと、
「このクラスは、こういうメソッドを必ず持っていますよ」と 約束(契約)するための仕組み
だと思って読んでいただくと、イメージしやすいです。
インターフェースとは何か
「仕様だけを表す、純粋な契約書」
インターフェースは、
「このインターフェースを実装するクラスは、 このメソッドやプロパティを必ず持っています」
という “契約” を表現するための型です。
特徴をざっくりまとめると、
interfaceキーワードで宣言する- インスタンスは作れない(
newできない) - メソッドやプロパティの「形」だけを書く(中身は書かない)
- クラス側が「実装します」と宣言して、中身を書く
という感じです。
抽象クラスと似ていますが、 インターフェースは 「純粋に仕様だけ」を表すところが大きな違いです。
まずはシンプルなインターフェースを作ってみる
ログ出力のインターフェース ILogger
よくある例として、「ログを出力する機能」を考えてみます。
// ログ出力のインターフェース
interface ILogger
{
// メッセージをログに書き出すメソッド
void Log(string message);
}
C#ここでは、
interface ILogger→ 「ILogger というインターフェース」を宣言しているvoid Log(string message);→ 「Log というメソッドを必ず持つべき」という仕様だけを書いている
中身(本体)は書いていません。 インターフェースは 「形だけ」を定義するものだからです。
インターフェースを「実装する」クラス
コンソールにログを出す ConsoleLogger
インターフェースを使うときは、 クラス側で「このインターフェースを実装します」と宣言します。
using System;
// ILogger を実装するクラス:コンソールにログを出す
class ConsoleLogger : ILogger
{
// インターフェースで定義された Log メソッドを実装する
public void Log(string message)
{
Console.WriteLine($"[Console] {message}");
}
}
C#ここでのポイントは、
class ConsoleLogger : ILogger→ 「ConsoleLogger は ILogger を実装するクラスです」という宣言public void Log(string message)→ インターフェースで定義されたLogメソッドの中身を実際に書いている
インターフェースは「契約書」、 クラスは「契約を守る実装者」というイメージです。
ファイルにログを出す FileLogger(イメージ)
同じ ILogger を使って、 別の実装を作ることもできます。
using System.IO;
// ILogger を実装するクラス:ファイルにログを出す
class FileLogger : ILogger
{
private readonly string filePath;
public FileLogger(string filePath)
{
this.filePath = filePath;
}
public void Log(string message)
{
// 簡易的な例:毎回追記する
File.AppendAllText(filePath, $"[File] {message}{Environment.NewLine}");
}
}
C#ここでも、
FileLoggerはILoggerを実装しているLogメソッドの「形」は同じだが、中身はコンソールではなくファイル出力
という違いがあります。
重要ポイント:
- インターフェースは「メソッドの形」を決めるだけ
- 実際の動きは、実装するクラスごとに自由に変えられる
インターフェースを使う側のコード
「ILogger として扱う」ことで、実装を意識しない
インターフェースの真価は、 「実装の違いを意識せずに、共通の形で扱える」ところにあります。
class Program
{
static void Main(string[] args)
{
// ILogger 型の変数に、ConsoleLogger のインスタンスを入れる
ILogger logger = new ConsoleLogger();
logger.Log("コンソールにログを出します。");
// 別の実装に差し替えることも簡単
logger = new FileLogger("log.txt");
logger.Log("ファイルにログを出します。");
Console.ReadLine();
}
}
C#ここでは、
- 変数の型はずっと
ILogger - 実際に中に入っているのは
ConsoleLoggerやFileLogger - 呼び出しているメソッドは常に
logger.Log(...)
という構造になっています。
重要ポイント:
- インターフェースを使うと、 「呼び出す側は仕様(メソッドの形)だけを意識し、 実装の違いを気にしなくてよくなる」
- 実装を差し替えても、呼び出し側のコードをほとんど変えずに済む
これは、アプリが大きくなったときに、 保守性・拡張性を大きく高めてくれる考え方です。
抽象クラスとの違いを整理する
抽象クラス
abstract classで宣言- フィールド・プロパティ・通常メソッド・抽象メソッドを持てる
- 共通処理を中に書ける
- 1つのクラスは、基本的に1つの抽象クラスしか継承できない
インターフェース
interfaceで宣言- メソッドやプロパティの「形」だけを書く(中身は書かない)
- 共通処理は持たない(C# のバージョンによっては default 実装もありますが、入門では「仕様だけ」と考えてOKです)
- 1つのクラスは、複数のインターフェースを同時に実装できる
ざっくり言うと、
抽象クラス: 「共通仕様+共通処理」をセットで提供する“設計図”
インターフェース: 「共通仕様だけ」を提供する“契約書”
というイメージで捉えると分かりやすいです。
複数インターフェースを実装する例
「保存できる」「読み込める」という2つの契約
インターフェースの強みのひとつは、 複数のインターフェースを同時に実装できることです。
// 保存の契約
interface ISaveable
{
void Save(string path);
}
// 読み込みの契約
interface ILoadable
{
void Load(string path);
}
C#これを両方実装するクラスを作ります。
using System;
// 設定情報を保存・読み込みできるクラス
class Settings : ISaveable, ILoadable
{
public int Volume { get; set; }
public int Brightness { get; set; }
public void Save(string path)
{
Console.WriteLine($"設定を {path} に保存します。(Volume={Volume}, Brightness={Brightness})");
// 実際の保存処理は省略
}
public void Load(string path)
{
Console.WriteLine($"{path} から設定を読み込みます。");
// 実際の読み込み処理は省略。ここでは仮の値を設定
Volume = 50;
Brightness = 70;
}
}
C#使う側はこうです。
class Program
{
static void Main(string[] args)
{
Settings settings = new Settings();
// ISaveable として扱う
ISaveable saver = settings;
saver.Save("settings.json");
// ILoadable として扱う
ILoadable loader = settings;
loader.Load("settings.json");
Console.ReadLine();
}
}
C#ここでは、
SettingsはISaveableとILoadableの両方を実装している- 呼び出す側は「保存できるもの」「読み込めるもの」として、 インターフェース型で扱っている
という構造になっています。
重要ポイント:
- インターフェースは「役割」を表すのに向いています。
ISaveable:保存できるものILoadable:読み込めるもの
- 1つのクラスが複数の役割を持つことを、 インターフェースで自然に表現できます。
インターフェースの基本テンプレート
最後に、インターフェースの基本形をテンプレートとしてまとめます。
インターフェース
// 共通仕様を表すインターフェース
interface ITask
{
// タスクを実行するメソッド
void Execute();
// タスク名を取得するプロパティ
string Name { get; }
}
C#実装クラスA
class EmailTask : ITask
{
public string Name { get; } = "メール送信タスク";
public void Execute()
{
Console.WriteLine("メールを送信しています...");
}
}
C#実装クラスB
class ReportTask : ITask
{
public string Name { get; } = "レポート生成タスク";
public void Execute()
{
Console.WriteLine("レポートを生成しています...");
}
}
C#使う側
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
List<ITask> tasks = new List<ITask>
{
new EmailTask(),
new ReportTask()
};
foreach (var task in tasks)
{
Console.WriteLine($"タスク: {task.Name}");
task.Execute();
}
Console.ReadLine();
}
}
C#このテンプレートをベースに、
- インターフェースで「共通仕様」を決める
- 実装クラスで「中身」を自由に書く
- 呼び出し側はインターフェース型で扱う
という流れを、自分のテーマ(商品処理、ユーザー処理、ファイル処理など)で試してみると、 インターフェースの感覚がぐっと身近になります。
Day 44 のまとめ
Day 44 では、
interface→ 共通仕様を表す「契約書」のような型- 契約 → 「このインターフェースを実装するクラスは、このメソッドを必ず持つ」という約束
- 実装 → クラス側がインターフェースを受け取り、そのメソッドの中身を書いて約束を守ること
を、ログ出力や設定保存、タスクの例を通して学びました。
インターフェースは、
「実装の違いを意識せずに、 共通の形でオブジェクトを扱うための仕組み」
であり、 大きなアプリケーションではほぼ必ず登場する重要な概念です。
ぜひ、
- 自分でインターフェースを1つ作り
- それを実装するクラスを2〜3個作って
- インターフェース型のリストでまとめて扱ってみる
という小さな練習を通して、 インターフェースを 「難しい言葉」ではなく、 設計を柔らかく、拡張しやすくするための頼れる道具として、少しずつ体に馴染ませていってください。
