インターフェース vs 抽象クラス
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 26/70。
インターフェースと抽象クラスはどちらも、他のクラスが従うべき契約を定義することを可能にしますが、これらは異なる目的を持ち、それぞれ独自の機能を備えています。
インターフェースは、実装を持たずに、クラスが何をすべきかを定義します。これは純粋な契約です:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}抽象クラスは、規約(コントラクト)と共有の実装の両方を提供できます。これは部分的な設計図です:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // 実装する必要があります
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}主な違いは、継承ルールと目的にあります。
クラスは複数のインターフェースを実装できますが、継承できる抽象クラスは1つだけです。
関連のないクラスが振る舞いを共有する必要がある場合は、インターフェースを使用します(鳥と飛行機のための IFlyable など)。関連するクラスが共通のコードや状態を共有する場合は、抽象クラスを使用します(エンジンの始動メカニズムを共有する乗り物のように)。
抽象クラスは、コンストラクター、フィールド、およびメンバーへのアクセス修飾子を持つことができます。インターフェイスは伝統的にこれらを持つことができませんでしたが、モダンな C# ではデフォルト実装が許可されています。
迷ったときは、柔軟性を考慮してインターフェースを優先してください。共通の実装が必要になった場合は、後からいつでも抽象クラスを追加できます。
チャレンジ
簡単インターフェースと抽象クラスをいつ使い分けるかを示すメディアプレーヤーシステムを構築しましょう。関連のないデバイスがインターフェースを通じて再生動作を共有し、関連のあるメディアタイプが抽象クラスを通じて共通の実装を共有する設計を作成します。
コードは4つのファイルに分けて構成します:
IPlayable.cs:Media名前空間にIPlayableというインターフェースを定義します。このインターフェースは、スマートフォン、テレビ、専用の音楽プレーヤーなど、メディアを再生できるあらゆるものの契約を表します。文字列を返す単一のメソッドPlay()を宣言する必要があります。MediaFile.cs:Media名前空間にMediaFileという抽象クラスを定義します。これはすべてのメディアファイルの共有基盤を表します。Titleプロパティ(string)とDurationプロパティ(int、秒単位)を持つ必要があります。両方の値を設定するコンストラクタを含めてください。"{Title} ({Duration}s)"を返すGetInfo()という具体的なメソッドを追加します。また、文字列を返す抽象メソッドGetType()を宣言します。各メディアタイプは、それがどのような種類のファイルであるかを定義します。AudioFile.cs:Media名前空間に、MediaFileを継承しIPlayableを実装するAudioFileクラスを定義します。Artistプロパティ(string)を追加します。コンストラクタは、タイトル、再生時間、アーティストを受け取る必要があります。GetType()をオーバーライドして"Audio"を返すようにします。Play()を実装して"Playing audio: {Title} by {Artist}"を返すようにします。Program.cs: メインファイルで、入力値を使用してAudioFileを作成します。クラスの両方の側面を実演します。GetInfo()とGetType()(抽象クラスから継承)を呼び出し、次にPlay()(インターフェースから)を呼び出します。オーディオファイルをIPlayable変数に格納し、もう一度Play()を呼び出してインターフェースのポリモーフィズムを示します。
3つの入力を受け取ります:
- 曲のタイトル
- 秒単位の再生時間
- アーティスト名
次の形式で出力を表示してください:
Info: {GetInfo() result}
Type: {GetType() result}
Direct: {Play() result}
Via Interface: {Play() result from IPlayable variable}例えば、入力がBohemian Rhapsody、354、Queenの場合、出力は次のようになります:
Info: Bohemian Rhapsody (354s)
Type: Audio
Direct: Playing audio: Bohemian Rhapsody by Queen
Via Interface: Playing audio: Bohemian Rhapsody by QueenAudioFileが両方のアプローチからどのように恩恵を受けているかに注目してください。抽象クラスから共有実装(GetInfo())を継承しつつ、IPlayableの契約も満たしています。抽象クラスは関連するメディアタイプにコードの再利用を提供し、インターフェースは継承階層を共有することなく、関連のないクラスを「再生可能」にすることを可能にします。
チートシート
インターフェースは、実装を持たずに、クラスが従うべきコントラクト(規約)を定義します:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}抽象クラスは、コントラクトと共有される実装の両方を提供します:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // Must be implemented
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}主な違い:
- クラスは複数のインターフェースを実装できますが、継承できる抽象クラスは1つだけです
- 関連のないクラス間で振る舞いを共有する必要がある場合は、インターフェースを使用します
- 関連のあるクラス間で共通のコードや状態を共有する場合は、抽象クラスを使用します
- 抽象クラスは、コンストラクタ、フィールド、およびアクセス修飾子を持つことができます
- 柔軟性を高めるには、インターフェースを優先します
自分で試してみよう
using System;
using Media;
class Program
{
public static void Main(string[] args)
{
// 入力値を読み込む
string title = Console.ReadLine();
int duration = Convert.ToInt32(Console.ReadLine());
string artist = Console.ReadLine();
// TODO: 入力値を使用して AudioFile を作成する
// TODO: Info: に続けて GetInfo() の結果を出力する
// TODO: Type: に続けて GetType() の結果を出力する
// TODO: Direct: に続けて Play() の結果を出力する
// TODO: オーディオファイルを IPlayable 変数に格納する
// TODO: Via Interface: に続けて IPlayable 変数から Play() の結果を出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
5ポリモーフィズムとインターフェース
コンパイル時 vs 実行時ポリモーフィズムインターフェース vs 抽象クラス複数のインターフェース明示的なインターフェースアップキャストとダウンキャストまとめ - 図形計算機