
概要
端的にお聞きしたい事は、「intefaecはいつ使うのか」という事です。
抽象クラスとinterfaceの違いについて言及されてる記事等を参考にしましたが、
初学者の私にはどうしても「それって、結局抽象クラスでいいんじゃないか?」って思ってしまう点がおおいです。。
例
例えば、良く見るのが、下記のようなCommunicationインターフェイスがあり、
それを実装した Human
クラスや、 Animal
・ Alian
クラスを使った例があります。
が、これって結局抽象クラスでも何も問題ないような気がしてます。
php
1interface Communication 2{ 3 public function eat(); 4 public function doSleep(); 5 public function talk(); 6}
改めて質問
個人的に、まだインターフェイスって結局抽象クラスでまかなえるじゃん?って印象が強く、
インターフェイスを使う場面やメリットがわかっていないので、
この点をご教示頂けると幸いです。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答4件
0
すいません、長文です。
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 01:43
編集2016/01/31 01:54総合スコア21741
0
まだ回答を求められているか分かりませんが、回答してみます。
「それって、結局抽象クラスでいいんじゃないか?」ということを感じるのは至極当然で、抽象クラスのほうができることが多いのですね。
インターフェースはできることに制限があり、その制限を活かしたい場合に使うというイメージで私は理解しています。
ですので、抽象クラスだけで事足りているのであれば無理に使うことはないと思います。
インターフェースを使いたくなるような具体例を考えてみます。
(他のスレッドに記載した内容とほぼ同様の内容ですが・・)
家の中をクラス分けしてみましょう。
抽象クラスとして「建物」「家具」「家電製品」を作ったとします。
具体的なクラスを作ってみます。「家具」を継承するのは机、椅子、ベッド等があるとします。「家電製品」を継承するのは冷蔵庫や電子レンジ、ドライヤーがあるとします。
さて家電製品を使うにはコンセントが必要ですね。というわけで、抽象クラス「家電製品」に抽象メソッドとして「コンセント」を追加してみました。これで、「家電製品」を継承した各クラスはコンセントを実装する必要があります。
ここで一つ問題が出てきます。こたつや勉強机など、実は「家具」にもコンセントを必要とするものがあります。それに後で気づきました。
コンセントは「家電製品」の抽象メソッドですね。ということは、こたつや勉強机は、「家具」であり「家電製品」でもあるように、両方を多重継承すればよいのではないかと思われます。
ここまで考えればある程度気づかれるかと思いますが、「コンセント」という抽象メソッドを「家電製品」固有のものにしてしまうと、その他で「コンセント」を使いたくなったときに、「家電製品」の定義が大きすぎるのですね。多重継承自体を許していない言語仕様もありますし。
インターフェースは「実装すべきメソッド」を定義しておくためだけのものですが、上記のような場合であれば、「コンセント」はインターフェースとして定義しておいて、必要に応じて利用すればよいのです。
具体的には、机、椅子、ベッドは抽象クラスとして「家具」を継承、こたつや勉強机は抽象クラスとして「家具」を継承した上でインターフェース「コンセント」を利用。冷蔵庫や電子レンジ、ドライヤーなどは抽象クラス「家電製品」を継承した上でインターフェース「コンセント」を利用すればよい。
こんな感じで使っていくのがよい方法だと思います。
ただ、当然ながら、「家電製品」だけを作るような会社であれば特にコンセントが家電製品以外で使われることを想定しなくても良いので、特にインターフェースとしてコンセントを用意しなくても抽象クラス家電製品にコンセントを含めてしまえばよいです。そういう会社であれば、そういう方針でも全く問題ありません。
要は抽象クラスもインターフェースも使い方なのですが、使い分け方をイメージできる助けになれれば幸いです。
投稿2017/03/28 08:45
総合スコア1947
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
個人的には、僕が質問した
新仕様、覚えてますか?
https://teratail.com/questions/24863
のraccyさんの回答の「ただ、PHPは「他の言語がこうしているから、とりあえず足そう」って傾向が強いんですよね。」が、全てを物語っている気がします。
昔はinterface使いましたが今はやめました。利点が見いだせませんでした…
投稿2016/01/30 10:20

退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
クラスを多重継承する事はできません。
しかし、インターフェースなら複数を実装できます。
投稿2016/01/30 10:31
総合スコア2183
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。


退会済みユーザー
2016/01/31 03:42

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2016/01/31 03:39
2016/01/31 05:31
退会済みユーザー
2016/01/31 07:21
退会済みユーザー
2018/03/07 10:54