基礎から学ぶC#入門 90日コース | 継承・抽象化・インターフェース - Day 44:インターフェース

C# 90日で身につけるC#
スポンサーリンク
スポンサーリンク

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#

ここでも、

  • FileLoggerILogger を実装している
  • 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
  • 実際に中に入っているのは ConsoleLoggerFileLogger
  • 呼び出しているメソッドは常に 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#

ここでは、

  • SettingsISaveableILoadable の両方を実装している
  • 呼び出す側は「保存できるもの」「読み込めるもの」として、 インターフェース型で扱っている

という構造になっています。

重要ポイント:

  • インターフェースは「役割」を表すのに向いています。
    • 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個作って
  • インターフェース型のリストでまとめて扱ってみる

という小さな練習を通して、 インターフェースを 「難しい言葉」ではなく、 設計を柔らかく、拡張しやすくするための頼れる道具として、少しずつ体に馴染ませていってください。

タイトルとURLをコピーしました