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

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

新規登録して質問してみよう
ただいま回答率
85.50%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

4回答

6352閲覧

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

ryuuinn

総合スコア75

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

5グッド

8クリップ

投稿2016/01/30 10:15

概要

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

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

php

1interface Communication 2{ 3 public function eat(); 4 public function doSleep(); 5 public function talk(); 6}

改めて質問

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

aaaaaaaa, 7968, ikuwow, KIYZ👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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
raccy

総合スコア21733

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/01/31 03:39

とても良い説明だと思うのですが、やはり、PHPを普通に使っていてinterfaceやtraitが必要になってくるケースが思い浮かびません。PHPer失格ですね… 昔、ゲーム作っていた時は便利だったんですけどね… スライムと、ベススライムとメタルスライム作る時とか…
raccy

2016/01/31 05:31

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

退会済みユーザー

2016/01/31 07:21

ありがとうございます。CakePHP3のコードを読んでみます。 実は、2以前のCakeが大嫌いです。嫌いな理由は「Modelはデータベースにアクセスするためのもの」という立ち位置の間違った考え方です。DBアクセスの抽象化の仕方もゴミのようなので、本当に嫌いです。見たくもありません。 ですが、raccyさんがおっしゃるなら、泣きながらCake3のコードを読み解こうと思います。
退会済みユーザー

退会済みユーザー

2018/03/07 10:54

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

0

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

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

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

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

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

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

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

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

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

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

投稿2017/03/28 08:45

akabee

総合スコア1947

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

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

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

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

投稿2016/01/30 10:20

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

投稿2016/01/30 10:31

Stripe

総合スコア2183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/01/30 10:47

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

2016/01/30 18:27

例えば、Javaでは以下のようなインターフェースがあります。 ・Cloneable このオブジェクトは複製が可能 ・Comparable このオブジェクトは比較が可能 ・Runnable このオブジェクトは実行が可能 複製ができて、比較もできて、実行もできるオブジェクトを作る事は可能なため、複数のインターフェースを実装するケースはあります。
退会済みユーザー

退会済みユーザー

2016/01/31 03:42

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問