Menu
Coddy logo textTech

明示的なインターフェース

CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 28/70。

クラスが同じ名前のメンバーを持つ複数のインターフェースを実装する場合、名前の衝突が発生します。明示的なインターフェースの実装は、インターフェースごとに個別の実装を提供できるようにすることで、この問題を解決します。

明示的な実装では、メンバー名の前にインターフェース名を付け、アクセス修飾子を省略します。

public interface IAmericanPlug
{
    void Connect();
}

public interface IEuropeanPlug
{
    void Connect();
}

public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
    void IAmericanPlug.Connect()
    {
        Console.WriteLine("Connected to 120V");
    }
    
    void IEuropeanPlug.Connect()
    {
        Console.WriteLine("Connected to 220V");
    }
}

明示的に実装されたメンバーは、クラス自体からではなく、インターフェイス型の変数を通じてのみアクセスできます。

var adapter = new UniversalAdapter();
// adapter.Connect();  // コンパイルされません!

IAmericanPlug american = adapter;
american.Connect();  // 120Vに接続されました

IEuropeanPlug european = adapter;
european.Connect();  // 220Vに接続されました

この手法は、インターフェースのメンバーをクラスのパブリックAPIから隠したい場合にも役立ちます。オブジェクトを特定のインターフェースとして扱う場合にのみメソッドが意味をなす場合、明示的な実装を行うことで、コントラクト(契約)を満たしつつ、クラスの公開範囲(サーフェスエリア)をクリーンに保つことができます。

challenge icon

チャレンジ

簡単

明示的なインターフェースの実装によって、単一のクラスが使用されているインターフェースに応じて異なる動作を提供する方法を示すレポート生成システムを構築しましょう。表示用と印刷用で異なる形式にフォーマットできるドキュメントを作成します。

コードは4つのファイルに分けて構成します:

  • IDisplayable.cs: Reports 名前空間に IDisplayable というインターフェースを定義します。このインターフェースは、画面に表示できるコンテンツを表します。文字列を返す Render() メソッドを宣言する必要があります。
  • IPrintable.cs: Reports 名前空間に IPrintable というインターフェースを定義します。このインターフェースは、プリンターに送信できるコンテンツを表します。これも文字列を返す Render() メソッドを宣言する必要があります。IDisplayable と名前の衝突が発生することに注意してください!
  • Report.cs: Reports 名前空間に、IDisplayableIPrintable の両方を実装する Report クラスを定義します。レポートには Title プロパティ(string)と Content プロパティ(string)を持たせ、両方の値を設定するコンストラクタを作成します。明示的なインターフェースの実装を使用して、異なる Render() の動作を提供します:
    • 表示用にレンダリングする場合: "[SCREEN] {Title}: {Content}"
    • 印刷用にレンダリングする場合: "[PRINT] {Title} | {Content}"
  • Program.cs: メインファイルで、入力値を使用して Report を作成します。明示的な実装はインターフェース変数を通じてのみアクセスできるため、同じレポートを IDisplayable 変数と IPrintable 変数の両方に格納し、それぞれで Render() を呼び出して、使用しているインターフェースに基づいて同じオブジェクトがどのように異なる出力を生成するかを確認します。

2つの入力を受け取ります:

  • レポートのタイトル
  • レポートの内容

次の形式で出力を表示してください:

Display version:
{Render() via IDisplayable}
Print version:
{Render() via IPrintable}

例えば、入力が Sales ReportQ4 revenue increased by 15% の場合、出力は次のようになります:

Display version:
[SCREEN] Sales Report: Q4 revenue increased by 15%
Print version:
[PRINT] Sales Report | Q4 revenue increased by 15%

アクセスに使用するインターフェースの参照によって、同じ Report オブジェクトがどのように異なる動作をするかに注目してください。これが明示的なインターフェース実装の力です。名前の衝突を解決しながら、コンテキストに適した動作を提供します!

チートシート

クラスが同じ名前のメンバーを持つ複数のインターフェースを実装する場合、明示的なインターフェースの実装を使用して、各インターフェースに対して個別の実装を提供できます。

インターフェースのメンバーを明示的に実装するには、メンバー名の前にインターフェース名を付け、アクセス修飾子を省略します。

public interface IAmericanPlug
{
    void Connect();
}

public interface IEuropeanPlug
{
    void Connect();
}

public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
    void IAmericanPlug.Connect()
    {
        Console.WriteLine("Connected to 120V");
    }
    
    void IEuropeanPlug.Connect()
    {
        Console.WriteLine("Connected to 220V");
    }
}

明示的に実装されたメンバーは、インターフェース型の変数を通じてのみアクセスでき、クラス自体を通じてアクセスすることはできません。

var adapter = new UniversalAdapter();
// adapter.Connect();  // コンパイルされません!

IAmericanPlug american = adapter;
american.Connect();  // Connected to 120V

IEuropeanPlug european = adapter;
european.Connect();  // Connected to 220V

この手法は、以下の場合に役立ちます。

  • インターフェースメンバー間の名前の衝突を解決する
  • 特定のインターフェースのコンテキストでのみ意味をなす場合に、クラスのパブリックAPIからインターフェースメンバーを隠す

自分で試してみよう

using System;
using Reports;

class Program
{
    public static void Main(string[] args)
    {
        // 入力を読み込む
        string title = Console.ReadLine();
        string content = Console.ReadLine();

        // TODO: title と content を使用して Report オブジェクトを作成する

        // TODO: レポートを IDisplayable 変数に格納する

        // TODO: 同じレポートを IPrintable 変数に格納する

        // TODO: IDisplayable の Render() を呼び出してディスプレイ版を表示する
        Console.WriteLine("Display version:");
        // ここでレンダリング結果を表示する

        // TODO: IPrintable の Render() を呼び出して印刷版を表示する
        Console.WriteLine("Print version:");
        // ここでレンダリング結果を表示する
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン