Menu
CoddyTech

インターフェース 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つだけです。

クラスは抽象クラスを継承しながら、同時にインターフェースを実装することもできます。コロンの後に、まず基底クラスを記述し、その後にインターフェースをカンマで区切って記述します。

public class Airplane : Vehicle, IFlyable
{
    public override void Drive()
    {
        Console.WriteLine("Taxiing to the runway");
    }

    public void Fly()
    {
        Console.WriteLine("Flying above the clouds");
    }
}

鳥と飛行機に共通するIFlyableのような振る舞いを、関連のないクラス間で共有する必要がある場合は、インターフェースを使います。車両がエンジンの始動方法を共有する場合のように、関連するクラス間で共通のコードと状態を共有する場合は、抽象クラスを使います。

抽象クラスには、コンストラクター、フィールド、メンバーのアクセス修飾子を持たせることができます。従来、インターフェイスにはこれらを持たせることはできませんでしたが、現代の C# では既定の実装が可能です。

迷った場合は、柔軟性を重視してインターフェースを選びましょう。共通の実装が必要になったら、後から抽象クラスを追加できます。

challenge icon

チャレンジ

簡単

インターフェースと抽象クラスをいつ使うべきかを示すメディアプレーヤーシステムを作りましょう。関連のないデバイスがインターフェースを通じて再生動作を共有し、関連するメディアの種類が抽象クラスを通じて共通の実装を共有する設計を作成します。

コードを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)を追加します。コンストラクターは title、duration、artist を受け取ります。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 Queen

AudioFile が両方のアプローチの利点を得ていることに注目してください。抽象クラスから共通の実装(GetInfo())を継承すると同時に、IPlayable の契約も満たしています。抽象クラスは関連するメディアの種類でコードを再利用できるようにし、インターフェースは継承階層を共有しなくても、関連のない任意のクラスが「再生可能」になれるようにします。

自分で試してみよう

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()の結果を出力する
    }
}
quiz icon腕試し

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

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

自分で練習してみよう: C#オンラインコンパイラ