CodeGym /コース /C# SELF /複数インターフェースの実装

複数インターフェースの実装

C# SELF
レベル 23 , レッスン 2
使用可能

1. なんで複数インターフェースを実装するの?

リアルなシステムを設計するとき、オブジェクトはよく1つの「役割」だけじゃなくて、いくつも持つことが多いんだ。例えば、電子書籍があって、それは読むだけじゃなくて、編集もできるし、クラウドに保存もできる。オブジェクト指向プログラミング(OOP)的に言うと、そのオブジェクトは複数のインターフェースを実装しなきゃいけないってことだね:

  • IReadable — コンテンツの読み取り。
  • IWritable — コンテンツの編集。
  • ISyncable — リモートストレージとの同期。

ここで複数インターフェース実装の出番!これがC#(てかOOP全体)のスーパーパワーで、クラスにはできないことなんだよ。クラスは2つ以上継承できないけど、インターフェースはいくらでも実装できる。

リアルライフの例え

会社の社員を想像してみて。バシャは同時に:

  • プログラマー(コード書く)
  • テスター(たまに他人のコードチェック)
  • マネージャー(スプリントやコーヒーのノルマ計画)

この「役割」には全然違う責任がある。でもバシャは全部の「契約」をちゃんとこなしてる!プログラミングでも同じで、クラスがインターフェースを実装して、その「責任」を引き受けるってわけ。

2. 複数インターフェース実装のシンタックス

めっちゃ簡単:クラス宣言のときにカンマで区切って並べるだけ:


public interface IReadable
{
    void Read();
}

public interface IWritable
{
    void Write(string text);
}

public class Note : IReadable, IWritable
{
    private string content = "";

    public void Read()
    {
        Console.WriteLine("メモ: " + content);
    }

    public void Write(string text)
    {
        content = text;
        Console.WriteLine("メモが更新された!");
    }
}

この例だとNoteクラスは両方のインターフェースを実装してる。だから、ReadWriteも両方のメソッドを実装しなきゃダメってこと。

3. 「うちら」のアプリの例で見ると

前の例では、口座操作用のシンプルなバンキングアプリを作ってたよね。今度は、印刷できる(IPrintable)、ファイルに保存できる(ISavable)、メール送信もできる(IEmailable)ドキュメント用の万能クラスが必要になったとしよう。

インターフェースを定義しよう:


public interface IPrintable
{
    void Print();
}

public interface ISavable
{
    void Save(string filePath);
}

public interface IEmailable
{
    void Email(string toAddress);
}

全部入りクラス:


public class Statement : IPrintable, ISavable, IEmailable
{
    public string Content { get; set; }

    public void Print()
    {
        Console.WriteLine("明細を印刷中…");
        Console.WriteLine(Content);
    }

    public void Save(string filePath)
    {
        // 標準ライブラリのFile.WriteAllTextを使う。
        File.WriteAllText(filePath, Content);
        Console.WriteLine($"明細がファイルに保存された: {filePath}");
    }

    public void Email(string toAddress)
    {
        Console.WriteLine($"明細がメールで送信された: {toAddress}(シミュレーション)");
    }
}

このクラスは好きなように使えるよ:


var stat = new Statement { Content = "今月の取引: +1000 ユニット, -500 クレジット." };
stat.Print();
stat.Save("statement.txt");
stat.Email("boss@bank.corp");

ちなみに、こういうオブジェクトを、必要なインターフェースだけ要求するメソッドに渡すのが超便利。他の機能は気にしなくてOK。例えば、印刷用メソッドはIPrintableだけ受け取ればよくて、中身が「保存屋」か「メール屋」か知らなくてもいい。

4. インターフェース参照の使い方:オブジェクトの「できること」はどこで見える?

ここからがマジック。複数インターフェースを実装したら、オブジェクトをその契約の1つとしてだけ扱える。例えば:


IPrintable printable = new Statement { Content = "抽象化のレクチャー" };
printable.Print(); // 印刷だけできる

// printable.Save("file.txt"); // エラー: IPrintableインターフェースはSaveを知らない。

でも、別のインターフェースに切り替えれば:


ISavable savable = printable as ISavable;
if (savable != null)
{
    savable.Save("file.txt");
}

これは、特定のインターフェース参照を受け取るメソッドにオブジェクトを渡すときに便利。こうすると依存が減って、コードがめっちゃ柔軟になる。新しい実装を追加しても、古いコードを変える必要なし!

5. 複数実装と、違うインターフェースで同じ名前のメソッド

ここが面白いところ!もし2つのインターフェースが同じ名前だけど意味が違うメソッドを要求してたら?例えば、コーヒーマシン用のインターフェースを考えてみよう:


public interface IStartable
{
    void Start();
}

public interface IRunnable
{
    void Start();
}

コーヒーマシンは「スタートできる」(IStartable — ドリンク作り開始)し、「ランナブル」(IRunnable — 全体的に動き出す)でもある。

デフォルト実装(普通のやつ):


public class CoffeeMachine : IStartable, IRunnable
{
    public void Start()
    {
        Console.WriteLine("コーヒーマシンが両方の役割でスタート!");
    }
}

この場合、1つのStart()実装が両方のインターフェースを「カバー」してる。

意味を分けたい場合は?

明示的インターフェース実装を使えばOK:


public class CoffeeMachine : IStartable, IRunnable
{
    void IStartable.Start()
    {
        Console.WriteLine("スタート: ドリンク作り開始!");
    }

    void IRunnable.Start()
    {
        Console.WriteLine("スタート: マシンが動作モードに入った。");
    }
}

この場合、特定の実装はインターフェース変数経由でしか呼べない:


CoffeeMachine cm = new CoffeeMachine();

IStartable startable = cm;
startable.Start(); // スタート: ドリンク作り開始!

IRunnable runnable = cm;
runnable.Start(); // スタート: マシンが動作モードに入った。

// cm.Start(); // コンパイルエラー!Startはクラスのメソッドとしては使えない。

ちなみに、このテクニックは.NETの標準ライブラリでもよく使われてる。例えば、クラスが複数の似たインターフェース(別フレームワーク由来)を実装してて、それぞれ違う動作が必要なときとかね。

6. 複数インターフェース実装と継承の違い

できること クラス(継承) インターフェース
基底型の数 1つだけ いくらでも
コードの継承 あり(基本実装できる) なし、シグネチャだけ(デフォルトメソッド除く)
状態の保持 あり なし
新しい役割の追加 不可(または複雑、コンポジション経由) 簡単、インターフェース実装するだけ
向いてる用途… 「物理的」な階層 「役割」や論理的な機能

7. 実践例:「ハイブリッド」オブジェクト

複数インターフェース実装のおかげで、不要な継承を気にせず、独自の「役割」セットを持つクラスを作れるよ。

例えば、うちらのバンキングアプリなら、自分の情報を保存できて、自分でバリデーションもできて、印刷もできるクラスを作れる。全部別々のインターフェースで:


public interface IValidatable
{
    bool Validate();
}

public class Check : IPrintable, ISavable, IValidatable
{
    public string Data { get; set; }

    public void Print()
    {
        Console.WriteLine("チェック印刷: " + Data);
    }

    public void Save(string filePath)
    {
        File.WriteAllText(filePath, Data);
        Console.WriteLine("チェックが保存された: " + filePath);
    }

    public bool Validate()
    {
        return !string.IsNullOrEmpty(Data);
    }
}

こういうやり方だと、必要に応じて役割を組み合わせて、拡張しやすいアーキテクチャが作れる。新しい「責任」が必要になったら、新しいインターフェースを実装するだけでOK。

8. よくあるミスと落とし穴

初心者がよくやるミス:インターフェースの全メンバーを実装し忘れる。コンパイラは容赦なくエラーを出してくるし、「クラスはインターフェースメンバーを実装しなきゃダメ」って教えてくれる。

もう1つよくある問題は、メソッドのアクセス範囲の混乱。明示的に実装したメソッドは、クラス変数からはアクセスできない。インターフェース経由だけ。

さらにありがちなのがリファクタ時の問題:インターフェースに変更(例えばメソッド追加)を加えたのに、全部の実装を更新し忘れる。これもコンパイルエラーになる。だから、設計時には、たくさんのクラスが使い始めた後はインターフェースを変えないようにしよう。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION