674
633

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

インターフェースと抽象クラスの使い分け、活用方法

Last updated at Posted at 2016-11-20

はじめに

インターフェースとか抽象クラスとか、使い方をよく分かっていませんでした。
色々試行錯誤してみて、最近その恩恵というものが分かってきたので、自分なりの解釈を記そうと思います。
その考え方は正しい、間違っているとか是非コメントお待ちしております。
今回は言語はC#で書きます。
JavaやPHPといった他のオブジェクト指向言語でも基本は通じるのでパッと見わかるような内容になっていると思います。

最初にインターフェース、抽象メソッドについて概要。
その次に実際のコードとしてどう活用していくかを記していきます。

そもそもインターフェースとか抽象クラスってなんぞや

まず、コードベースで見ていきます。

インターフェース

interface IFly
{
    void fly();
}

class Bird : IFly
{
    public void fly()
    {
        // 飛ぶ処理
    }
}

インターフェースであるIFly、それを継承するBirdがあるとします。
インターフェースではflyというメソッドが定義されています。
そのとき、継承先であるBirdはそのflyメソッドを定義しなければエラーとなります。
つまり、インターフェースは継承先に対して、定義したものの実装を強制することができます。

抽象クラス

対して、抽象クラスを見てみましょう。

abstract class AbstractHuman
{
    public void walk()
    {
        // 歩く処理
    }

    // 抽象メソッド
    abstract protected void talk();
}

class Human : AbstractHuman
{
    protected override void talk()
    {
        // 話す処理
    }
}

抽象クラスは、継承されることが前提となっています。
そのままインスタンス化するはできず、エラーが発生します。

var abstarctHuman = new AbstractHuman(); // エラー
var human = new Human(); // OK

抽象クラスでは、継承先に実装を強制することができるという点はインターフェースと同じです。
抽象メソッドという実装は行わず定義のみのメソッドを定義して、継承先に実装を強制することができます。
ここではtalkメソッドを継承先で定義しないとエラーになります。

抽象クラスはそれに加えて、メソッドを実装することができます。
ここではwalkメソッドはそのままAbstarctHumanクラス内で、実コードを書いて実装することが可能です。

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

あれ?インターフェースも抽象クラスもメソッドの実装の強制ができる?
ならそのまま実装も行える抽象クラスで全部やったほうがいいんじゃね?
インターフェースいらなくね?

そんなときに出てくるのが、多重継承の問題です。
基本的にはクラスは複数のクラスを継承する多重継承は行えません。
それは抽象クラスも同様です。

対して、インターフェースは複数の継承を行うことができます。
先ほどのインターフェースのコードに変更を加えていきます。
鳴く処理の実装を強制したいと思います。

interface IFly
{
    void fly();
}

// 追加
interface IChirp
{
    void chirp();
}

class Bird : IFly, IChirp
{
    public void chirp()
    {
        // 鳴く処理
    }

    public void fly()
    {
        // 飛ぶ処理
    }
}

IChirpというchirpというメソッドを定義したインターフェースを追加してみました。
鳥によっては鳴かない鳥、飛ばない鳥もいるでしょう。
インターフェースが分かれているのと、インターフェースは多重継承できるという仕様上、継承するしない、複数継承といった使い分けができるのです。

別の見方

インターフェースは外部向け、抽象クラスは内部向けといったイメージです。
どういったことかというと、C#(おそらく他言語でも)ではインターフェースでの定義ではアクセス修飾子が全てpublicに強制されます。
ということは、インターフェースで定義されるメソッドは、基本的には外部からインスタンス化した上で、そのメソッドを呼び出されることになります。
正に名前の通り、インターフェースというわけです。
それに対して抽象クラスの抽象メソッドは、アクセス修飾子がpublic、またはprotectedとなります。
もちろんここでもpublicで外部向けに実装を強制することができるのですが、protectedの実装を強制できるので、内部で呼び出すメソッドの実装を強制することが可能となっています。

活用方法

何となくできることとか分かったけどどうやって使っていくの?
私も仕様は分かってはいるけど実際のコードとしてどう扱っていけばいいんだろうと考えていました。
業務で実際に試行錯誤した中での一例をあげたいと思います。

今回はインターフェースとして、オブジェクトを返すメソッドを定義します。
それを継承した上で、特定の文字列をオブジェクトに変換して返すメソッドを実装した抽象クラスを定義します。
抽象クラスには追加で上記の特定の文字列を生成する抽象メソッドを定義しておきます。
その抽象クラスを継承し、実際にインスタンス化されるクラスでは、上記の特定の文字列を生成する抽象メソッドを実装していきます。
ここでは特定の文字列を生成するメソッドは、TwitterAPIを使用してJSONを取得して返すという想定で行きます。

具体的に名前に落とし込んでいきます。

項目名 名前
インターフェース IObject
抽象クラス AbstractJsonApi
実装クラス TwitterApi

実装

では実際にコードで見ていきましょう。
まず、インターフェースです。

interface IObject
{
    object GetObject();
}

これで、IObjectインターフェースを継承したクラスは、object型を返すGetObjectメソッドを実装しなければいけません。

次に抽象クラスです。

abstract class AbstractJsonApi : IObject
{

    public object GetObject()
    {
        // ここで継承先でそれぞれの通信を実装
        var jsonString = GetJson();

        // jsonStringをオブジェクトにして返す(今回は省略)
        return new { json = jsonString };
    }

    protected abstract string GetJson();
}

IObjectインターフェースを継承してAbstractJsonApiという抽象クラスを実装してみました。
この中では先ほどのGetObjectメソッドが実装され、その上で抽象メソッドのGetJsonメソッドを定義しています。
ここで覚えておいてほしいのが、GetObjectというメソッドの実装の中で、GetJsonという抽象メソッドが呼び出されていることです。
これによって、継承先でその抽象メソッドを各々独自に実装したものを呼び出すことができるのです。
具体的なメリットは後述します。

次に実際にインスタンス化されて使用されるクラスです。

class TwitterApi : AbstractJsonApi
{
    protected override string GetJson()
    {
        // 通信して結果のオブジェクトを作成
        // APIからJSONを取得して返す
        return "TwitterAPIの結果のJSON";
    }
}

抽象クラスのAbstractJsonApiを継承したTwitterApiというクラスを実装しました。
抽象メソッドのGetJsonメソッドを実装しています。

説明

それぞれを定義、継承、実装を行うことによって何ができているかというと、まず、IObjectインターフェースによって、継承したクラスはGetObjectというobject型を返すメソッドがあることが分かるので、インスタンス化したときにそのメソッドを呼び出すことができるということが分かります。

var twitterApi = new TwitterApi();
Console.WriteLine(twitterApi.GetObject())

次に、抽象クラスではGetObjectメソッドのように、抽象メソッドを呼び出す実装をしておくことができます。
先述した通り、これによって継承先でそれぞれ独自の処理をかき分けておくことができ、最終的にすべて同じ処理を通すことができます。
例えばTwitterやInstagramといったAPIは取得方法も取得できるJSONの形式も違いますよね。
でも最終的にはJSON形式なので、JSON文字列をオブジェクト化する処理は共通化しておくと。
GetJsonメソッドでそれぞの仕様にあった取得処理を実装をしてあげることで、GetObjectメソッドを呼び出せば、それぞれのAPIのJSON文字列をオブジェクトとして変換した結果を取得することができるのです。

なので、抽象クラスを継承したクラスは下記のように、実装を局所的に変更して他の処理は共通化していくことが可能となってくるわけです。

class InstagramApi : AbstractJsonApi
{
    protected override string GetJson()
    {
        // 通信して結果のオブジェクトを作成
        // APIからJSONを取得して返す
        return "InstagramAPIの結果のJSON";
    }

}

class YoutubeApi : AbstractJsonApi
{
    protected override string GetJson()
    {
        // 通信して結果のオブジェクトを作成
        // APIからJSONを取得して返す
        return "YoutubeAPIの結果のJSON";
    }

}

呼び出し側も、IObjectインターフェースが実装されているため、GetObjectメソッドを共通で呼び出すことができます。

var twitterApi = new TwitterApi();
Console.WriteLine(twitterApi.GetObject());

var instagramApi = new InstagramApi();
Console.WriteLine(instagramApi.GetObject());

var youtubeApi = new YoutubeApi();
Console.WriteLine(youtubeApi.GetObject());

同様のインターフェースを持っているクラスであれば、こんな感じで同じメソッドを呼び出す処理なんかもできたりします。

// 上記のインスタンス
IObject[] objects = new IObject[] { twitterApi, instagramApi, youtubeApi };

foreach (IObject obj in objects)
{
    Console.WriteLine(obj.GetObject());
}

最後に

ほんの一例ですが、どうでしょうか。
依存性注入なんかでも活躍する場面が多いと思います。
PHPではタイプヒンティングも可能ですね。
他にもこう使うべきといった意見、お待ちしております。

674
633
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
674
633

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?