応用編

応用編5日目:インターフェース

抽象クラスをさらに発展させた、インターフェースの概念と使い方を学びましょう

応用編: 1 2 3 4 5 6 7

インターフェースとは

インターフェースの概念

抽象クラスの概念をさらに推し進めたのが、インターフェースという概念です。インターフェースもインスタンスを生成できませんが、抽象クラスとは根本的に異なります。インターフェースは、メソッドの実装やフィールドを一切持つことができません。メソッドの「呼び出し形式(シグネチャ)」のみを定義します。

インターフェースは「1つのクラスが複数の側面を持つ場合」によく使われます。例えば、携帯電話は「電話機能」と「メール機能」の両方を持っています。このような場合にインターフェースが有効です。

インターフェースの定義

インターフェースの宣言書式
interface (インターフェース名)
{
(メソッドのシグネチャ);

}

インターフェース内のメソッドは public であることが前提なので、アクセス修飾子を付ける必要はありません。また、abstractoverride も不要です。命名慣例として、インターフェース名の先頭に I を付けます(例:IPhoneIEmail)。

インターフェースの実装

インターフェースの実装書式
class (クラス名) : (インターフェース名), (インターフェース名), …
{

}

クラスは複数のインターフェースを同時に実装できます(カンマ区切り)。これは C# の単一継承の制約を超えて「複数の型として振る舞う」ことを可能にします。

サンプルプログラム

プロジェクト: SampleEx501 / ファイル名: IPhone.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx501
{
    interface IPhone
    {
        void Call(string number);
    }
}
プロジェクト: SampleEx501 / ファイル名: IEmail.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx501
{
    interface IEmail
    {
        void SendMail(string address);
    }
}
プロジェクト: SampleEx501 / ファイル名: CellPhone.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx501
{
    class CellPhone : IPhone, IEmail
    {
        private string mailAddress;
        private string number;

        public CellPhone(string mailAddress, string number)
        {
            this.mailAddress = mailAddress;
            this.number = number;
        }

        public void Call(string number)
        {
            Console.WriteLine(number + "に、" + this.number + "から電話をかけます。");
        }

        public void SendMail(string address)
        {
            Console.WriteLine(address + "に、" + this.mailAddress + "からメールを出します。");
        }
    }
}
プロジェクト: SampleEx501 / ファイル名: Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx501
{
    class Program
    {
        static void Main(string[] args)
        {
            CellPhone cp = new CellPhone("hoge@email.com", "090-1234-5678");
            cp.Call("011-123-4567");
            cp.SendMail("fuga@email.com");

            //  IPhone 型にキャストすると Call() しか使えなくなる
            IPhone phone = (IPhone)cp;
            phone.Call("011-987-6543");

            //  IEmail 型にキャストすると SendMail() しか使えなくなる
            IEmail mail = (IEmail)cp;
            mail.SendMail("bar@email.com");
        }
    }
}
実行結果
011-123-4567に、090-1234-5678から電話をかけます。
fuga@email.comに、hoge@email.comからメールを出します。
011-987-6543に、090-1234-5678から電話をかけます。
bar@email.comに、hoge@email.comからメールを出します。

インターフェースとキャスト

インターフェース型の変数にキャストすると、もとは同じクラスのインスタンスであっても、キャストした型で定義されているメンバしか使用できなくなります。例えば、IPhone 型にキャストした phoneCall() のみ呼び出せます。これにより、大規模開発でクラスの機能を意図的に制限し、余計なメンバへのアクセスを防ぐことができます。

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

サンプルプログラム

次のサンプルでは、1つのクラスが2つのインターフェースを実装しており、両方に共通するメソッド名が含まれています。

プロジェクト: SampleEx502 / ファイル名: IFuncs1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx502
{
    interface IFuncs1
    {
        void Func1();
        void Func2();
    }
}
プロジェクト: SampleEx502 / ファイル名: IFuncs2.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx502
{
    interface IFuncs2
    {
        void Func2();
        void Func3();
    }
}
プロジェクト: SampleEx502 / ファイル名: Dummy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx502
{
    class Dummy : IFuncs1, IFuncs2
    {
        public void Func1()
        {
            Console.WriteLine("Func1");
        }

        public void Func2()
        {
            Console.WriteLine("Func2");
        }

        public void Func3()
        {
            Console.WriteLine("Func3");
        }
    }
}
プロジェクト: SampleEx502 / ファイル名: Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx502
{
    class Program
    {
        static void Main(string[] args)
        {
            Dummy d = new Dummy();
            IFuncs1 i1 = (IFuncs1)d;
            IFuncs2 i2 = (IFuncs2)d;
            i1.Func1();
            i1.Func2();
            i2.Func2();
            i2.Func3();
        }
    }
}
実行結果
Func1
Func2
Func2
Func3

インターフェースと抽象クラスの違い

IFuncs1 と IFuncs2 の両方に Func2() が含まれていますが、Dummy クラスでは1つの実装で対応できます。インターフェースは「クラスの一部を切り取るためのもの」であり、メソッドの重複があってもかまいません。

抽象クラスとインターフェースの比較
項目抽象クラスインターフェース
フィールド持てる持てない
メソッドの実装持てる(一部)持てない
多重継承不可(単一のみ)複数実装可
目的共通機能の集約と拡張クラスの機能の切り取り・制限
💡 継承はクラスの機能を受け継いで拡張するもので、インターフェースはクラスが「何ができるか」を定義するものです。考え方が根本的に異なります。

練習問題(応用編)5 へ →