明示的なインターフェース
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から隠したい場合にも役立ちます。オブジェクトを特定のインターフェースとして扱う場合にのみメソッドが意味をなす場合、明示的な実装を行うことで、コントラクト(契約)を満たしつつ、クラスの公開範囲(サーフェスエリア)をクリーンに保つことができます。
チャレンジ
簡単明示的なインターフェースの実装によって、単一のクラスが使用されているインターフェースに応じて異なる動作を提供する方法を示すレポート生成システムを構築しましょう。表示用と印刷用で異なる形式にフォーマットできるドキュメントを作成します。
コードは4つのファイルに分けて構成します:
IDisplayable.cs:Reports名前空間にIDisplayableというインターフェースを定義します。このインターフェースは、画面に表示できるコンテンツを表します。文字列を返すRender()メソッドを宣言する必要があります。IPrintable.cs:Reports名前空間にIPrintableというインターフェースを定義します。このインターフェースは、プリンターに送信できるコンテンツを表します。これも文字列を返すRender()メソッドを宣言する必要があります。IDisplayableと名前の衝突が発生することに注意してください!Report.cs:Reports名前空間に、IDisplayableとIPrintableの両方を実装する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 Report と Q4 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:");
// ここでレンダリング結果を表示する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
5ポリモーフィズムとインターフェース
コンパイル時 vs 実行時ポリモーフィズムインターフェース vs 抽象クラス複数のインターフェース明示的なインターフェースアップキャストとダウンキャストまとめ - 図形計算機