質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.35%

PHPにおける抽象クラスとinterfaceの使い分け

受付中

回答 4

投稿

  • 評価
  • クリップ 8
  • VIEW 2,690

ryuuinn

score 73

 概要

端的にお聞きしたい事は、「intefaecはいつ使うのか」という事です。
抽象クラスとinterfaceの違いについて言及されてる記事等を参考にしましたが、
初学者の私にはどうしても「それって、結局抽象クラスでいいんじゃないか?」って思ってしまう点がおおいです。。

 例

例えば、良く見るのが、下記のようなCommunicationインターフェイスがあり、
それを実装した Human クラスや、 Animal ・ Alian クラスを使った例があります。
が、これって結局抽象クラスでも何も問題ないような気がしてます。

interface Communication
{ 
    public function eat();
    public function doSleep();
    public function talk();
}

 改めて質問

個人的に、まだインターフェイスって結局抽象クラスでまかなえるじゃん?って印象が強く、
インターフェイスを使う場面やメリットがわかっていないので、
この点をご教示頂けると幸いです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+8

すいません、長文です。

PHPは単一継承です。C++やPythonなどの多重継承ができる言語と違い、親クラスはただ一つのみです。まず、これが大前提になりますので、よく覚えておいてください。これを踏まえてなぜ抽象クラスの他にインターフェースがあるのか、そして、5.4.0以降からトレイトが新たに作られたのかを説明していきます。

抽象クラス(abstract class)

参考: PHP: クラスの抽象化 - Manual

さて、普通のクラスの他に抽象クラスがなぜあるかを考えてみましょう。

静的型付け言語では型の継承は多態性(ポリモーフィズム)を行うために重要な要素ですが、動的型付け言語ではわりとどうでもいいことです。ただこれはダック・タイピングが大好きなPythonやRubyのお話であって、PHPはアヒルがあまりお好きじゃないようです。型を意識するにはみんなをまとめる親クラスを作って子クラスを作って行く必要があります。でも、時には抽象的な、つまり、具体的なオブジェクトを作れず、実際の実装は子クラスに任せる親クラスをつくった方がいいときがあります。例えば「動物」クラスを作って「啼く」メソッドを実装しようとしても、具体的に「猫」なのか「犬」なのかまで決めないと実装しようもありません。そこで、「動物」クラスの「啼く」メソッドは抽象メソッドにして、それを拡張(extends)した子クラスである「猫」クラスや「犬」クラスに任せてしまうのです。抽象メソッドがあるクラスを具体的なオブジェクトにすることは出来ないので、「動物」クラスは抽象クラスにしてしまいます。また、動物共通の「息をする」メソッドとかは「動物」クラスに実装しておけば、他の「猫」クラスや「犬」クラスで実装する必要は無くなります。

こうして多態性を保ちながら、抽象化された親クラスを作れるようにしたのが抽象クラスです。型の継承という側面以外にも、抽象クラスは「抽象メソッドの実装を子クラスに強制させる」ことと「実装されたメソッドは子クラスでそのまま利用可能」という機能があります。ちゃんと実装されているかを確認できますし、コードの再利用もできる便利なものです。

インターフェース(interface)

参考: PHP: オブジェクト インターフェイス - Manual

抽象クラスには一つ問題がありました。それは複数の親クラスを持てないため、複数の抽象クラスを指定できないと言うことです。これを解決したのがインターフェースです。インターフェースは抽象メソッドだけを集めた特殊なクラスのようなものです。具体的な実装が出来ない代わりにインターフェースは複数指定できます。先ほどの「動物」クラスの子クラスにする「蝙蝠」を考えてみましょう。蝙蝠は飛びます、そこで「飛ぶ」メソッドがある「飛行生物」インターフェースを作って「蝙蝠」クラスに含めます(implements)。こうすることで、「蝙蝠」クラスには必ず「飛ぶ」が実装されていると言うことを強制できます。

つまり、インターフェースは「実装の強制」です。実装を強制することで、実装漏れを防ぎ、実行時にエラーが出ないようにできます。他の子クラスとの兼ね合いで親の抽象クラスでは強制できなかったメソッドについても、いくらでも強制し、その動作が保証できるようにします。また、そのインターフェースを含んでいるかどうかはinstanceofやタイプヒンティングでも検知できます。これは多態性を保証するにあたって、極めて重要なことです。

トレイト(trait)

参考: PHP: トレイト - Manual

インターフェースによって柔軟な「実装の強制」ができるようになりました。しかし、インターフェースには具体的に実装があるメソッドを含めることが出来ません。共通化したメソッドはなるべく多くのクラスで気軽に使いたいと思うはずです。そこで考えられたのがトレイトです。

トレイトは具体的な実装も出来るインターフェースのようなものです。。インターフェースと同じでトレイトはクラスの中でいくつでも使う(use)事が出来ます。しかし、トレイトで実装されたメソッドやプロパティはあたかもクラス内で実装されたかのように使うことができます。いってみれば、複数の抽象クラスを継承したような動きです。もし、メソッド名が被ってしまった場合は、指定された順番による優先順位を決めて、問題なく動くようにしています。

なお、トレイトはインターフェースと違い、他のトレイトを拡張(extends)するようなことは出来ないようにしているので、ダイヤモンド継承問題のような複雑な使用を覚える必要はありません(入れ子にはできます)。また、いわゆるMixinに近いような動作になりますが、PHPのトレイトはMixinとは違い平坦に展開されます。トレイトは継承するものではなく、使うものなのです。そのため、instanceofでトレイトが含まれているかどうかを検知できませんが、その役割をインターフェースに受け渡すことで、インターフェースが無用の長物にならないよう言語全体のバランスを取っています。

他の言語(参考)

他の言語にも似たような仕組みがありますが、詳細は異なります。

  • 多重継承であるC++には、抽象クラスはありますが、インターフェースとトレイトはありません。
  • 単一継承であるC#には、抽象クラスとインターフェースがありますが、トレイトはありません。Mixinしたい場合は、拡張メソッドとConditinalWeakTableを組み合わせることで同じようなことが出来るようです。
  • 単一継承であるJavaには、抽象クラスとインターフェースがありますが、トレイトはありません。ただし、Java8からはインターフェースでメソッドを具体的に実装することが可能になったため、Mixinができるようになりました。
  • 単一継承であるScalaには、抽象クラスとトレイトのみで、トレイトがインターフェースの役割を兼ねています。なお、Scalaのトレイトは実質Mixinであり、真のトレイトではありません
  • 多重継承であるPythonには、言語仕様としては抽象クラスもインターフェースもトレイトもありません。しかし、標準で入っているabcモジュールを使うことで抽象クラスを実現できます。
  • 単一継承であるRubyには、抽象クラスもインターフェースもトレイトありませんが、モジュール(Module)によるMixinがその役割を担っています。ただし、モジュールのMixinだけでは実装を強制することは出来きません。

多重継承が可能な場合、抽象クラスでインターフェースやトレイトの役割を担うことが出来ますので、多重継承の言語では通常インターフェースもトレイトもありません。その代わり、多重継承ではダイヤモンド継承問題など複雑な仕様を学ぶ必要があります。

PythonやRubyのようなスクリプト言語では、抽象クラスなどが持つ「実装の強制」が言語仕様としてはありません。コンパイルという課程がなく、動的な言語なため、解析時エラーでも実行時エラーでもあまり変わらないという思想から来ているのかもしれません。ただ、近年はスクリプト言語でも静的解析によるエラー検出が重要視されています。Pythonには標準でモジュールが提供されていますし、Rubyも今後開発される3.0系では実装される可能性があります。つまり、PHPは時代を先取りしています!

なお、ここまで読んで、「動的型付けのスクリプト言語で、ここまで型継承や実装強制を意識する必要があるのか?」とか「トレイトがあれば抽象クラスもインターフェースはいらないのでは?」とは思ってはいけません。そう思う時点でPHPer失格です。PHPを愛する私達は、そんなところも含めてPHPを愛する必要があるのです!

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/01/31 12:39

    とても良い説明だと思うのですが、やはり、PHPを普通に使っていてinterfaceやtraitが必要になってくるケースが思い浮かびません。PHPer失格ですね…

    昔、ゲーム作っていた時は便利だったんですけどね… スライムと、ベススライムとメタルスライム作る時とか…

    キャンセル

  • 2016/01/31 14:31

    そんなときは、CakePHP3のソースコードをみるとどんな風に使うといいかが参考になると思います。CakePHP3はクラス設計からかなりしっかり作り込まれているようで、基礎となる部分を抽象クラスとしており、イベントリスナとかはインターフェースにすることで分離し、共通で必要になるようなユーティティな部分はトレイトにして各クラスが使えるようにしています。他にも名前空間を使った構造をしていますので、名前空間やトレイトといった新しいPHPの仕様の使い方を学ぶのにお勧めです。

    キャンセル

  • 2016/01/31 16:21

    ありがとうございます。CakePHP3のコードを読んでみます。

    実は、2以前のCakeが大嫌いです。嫌いな理由は「Modelはデータベースにアクセスするためのもの」という立ち位置の間違った考え方です。DBアクセスの抽象化の仕方もゴミのようなので、本当に嫌いです。見たくもありません。

    ですが、raccyさんがおっしゃるなら、泣きながらCake3のコードを読み解こうと思います。

    キャンセル

  • 2018/03/07 19:54

    この回答、コメント欄まで含めて面白いですねw
    僕も cake3 読んでみます!

    キャンセル

+3

まだ回答を求められているか分かりませんが、回答してみます。
「それって、結局抽象クラスでいいんじゃないか?」ということを感じるのは至極当然で、抽象クラスのほうができることが多いのですね。
インターフェースはできることに制限があり、その制限を活かしたい場合に使うというイメージで私は理解しています。
ですので、抽象クラスだけで事足りているのであれば無理に使うことはないと思います。

インターフェースを使いたくなるような具体例を考えてみます。
(他のスレッドに記載した内容とほぼ同様の内容ですが・・)

家の中をクラス分けしてみましょう。

抽象クラスとして「建物」「家具」「家電製品」を作ったとします。
具体的なクラスを作ってみます。「家具」を継承するのは机、椅子、ベッド等があるとします。「家電製品」を継承するのは冷蔵庫や電子レンジ、ドライヤーがあるとします。

さて家電製品を使うにはコンセントが必要ですね。というわけで、抽象クラス「家電製品」に抽象メソッドとして「コンセント」を追加してみました。これで、「家電製品」を継承した各クラスはコンセントを実装する必要があります。

ここで一つ問題が出てきます。こたつや勉強机など、実は「家具」にもコンセントを必要とするものがあります。それに後で気づきました。
コンセントは「家電製品」の抽象メソッドですね。ということは、こたつや勉強机は、「家具」であり「家電製品」でもあるように、両方を多重継承すればよいのではないかと思われます。

ここまで考えればある程度気づかれるかと思いますが、「コンセント」という抽象メソッドを「家電製品」固有のものにしてしまうと、その他で「コンセント」を使いたくなったときに、「家電製品」の定義が大きすぎるのですね。多重継承自体を許していない言語仕様もありますし。
インターフェースは「実装すべきメソッド」を定義しておくためだけのものですが、上記のような場合であれば、「コンセント」はインターフェースとして定義しておいて、必要に応じて利用すればよいのです。

具体的には、机、椅子、ベッドは抽象クラスとして「家具」を継承、こたつや勉強机は抽象クラスとして「家具」を継承した上でインターフェース「コンセント」を利用。冷蔵庫や電子レンジ、ドライヤーなどは抽象クラス「家電製品」を継承した上でインターフェース「コンセント」を利用すればよい。
こんな感じで使っていくのがよい方法だと思います。

ただ、当然ながら、「家電製品」だけを作るような会社であれば特にコンセントが家電製品以外で使われることを想定しなくても良いので、特にインターフェースとしてコンセントを用意しなくても抽象クラス家電製品にコンセントを含めてしまえばよいです。そういう会社であれば、そういう方針でも全く問題ありません。

要は抽象クラスもインターフェースも使い方なのですが、使い分け方をイメージできる助けになれれば幸いです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

クラスを多重継承する事はできません。
しかし、インターフェースなら複数を実装できます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/01/30 19:47

    複数実装したいケースがいまひとつ思い浮かびませんが…

    キャンセル

  • 2016/01/31 03:27

    例えば、Javaでは以下のようなインターフェースがあります。
    ・Cloneable このオブジェクトは複製が可能
    ・Comparable このオブジェクトは比較が可能
    ・Runnable このオブジェクトは実行が可能

    複製ができて、比較もできて、実行もできるオブジェクトを作る事は可能なため、複数のインターフェースを実装するケースはあります。

    キャンセル

  • 2016/01/31 12:42

    はい、おっしゃっていることはよく分かっているんですが、実務でそれが必要になってくるケースが思い浮かばない感じです。
    経験不足で申し訳ありません。

    キャンセル

-1

個人的には、僕が質問した

新仕様、覚えてますか?
https://teratail.com/questions/24863

のraccyさんの回答の「ただ、PHPは「他の言語がこうしているから、とりあえず足そう」って傾向が強いんですよね。」が、全てを物語っている気がします。

昔はinterface使いましたが今はやめました。利点が見いだせませんでした…

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る