インターフェース != 抽象クラス

皆さんは”インターフェース”と”抽象クラス”の違いを説明できますか?
どちらも、対象のクラスに特定のメソッドの実装を強制させるもの、という性質は同じです。それでもインターフェースも抽象クラスも存在しているということは、必ずそれぞれの存在意義があるはずです。
とはいえ、携わっているのが大規模な開発じゃなかったりして存在は知っているものの実際にお目にかかったことは無いという方も実は多いのではないかと思います。
そこで、今回はインターフェースと抽象クラスの違いを、それぞれの働きから調べて行きたいと思います。
(※今回は自分が触れる機会の多いPHPでのインターフェースと抽象クラスの使い方について調べます)
インターフェースとは
基本的な使い方を復習します。まずはインターフェースです。
<?php
interface SampleInterface {
public function sampleMethod();
}
インターフェースはinterfaceで宣言します。上記ではSampleという名前のインターフェースを定義しています。
インターフェース内で宣言するのは一般的にメソッドのみです。そのメソッドも、上記の通り中身はまだ書けません。この名前のメソッドの実装を強制させます、という役割の宣言にすぎません。
ただし、インターフェースの特性により、内部で宣言されるメソッドのアクセス修飾子は全てpublicでなければいけません。
このインターフェースを適用させたいクラスにはimplementsを用います
<?php
interface SampleInterface {
public function sampleMethod();
}
class A implements SampleInterface {
}
こうすることで、AというクラスにSampleInterfaceというインターフェースを適用させることが出来ます。
ただし、上記のままではエラーになります。先ほどから書いている通り、インターフェースを適用されたクラスでは、そのインターフェースで宣言されているメソッドは必ず実装されていなければなりません。
つまり、
<?php
interface SampleInterface {
public function sampleMethod();
}
class A implements SampleInterface {
public function sampleMethod(){
}
}
これで大丈夫です。メソッドの中身が空であろうが、とにかくインターフェース内で宣言されているメソッドが全て実装されていれば問題ありません。
また、インターフェースは複数同時にクラスに適用させることができます。
<?php
interface SampleInterface1 {
public function sampleMethod1();
}
interface SampleInterface2 {
public function sampleMethod2();
}
class A implements SampleInterface1, SampleInterface2 {
public function sampleMethod1(){
}
public function sampleMethod2(){
}
}
このように列挙させることでインターフェースは複数適用させることが出来ます。その際、メソッドは適用されているインターフェース内のもの全てを実装する必要があります。
抽象クラスとは
続いて、抽象クラスの使い方についてです。
抽象クラスはその名の通り、abstract classで定義します。
<?php
abstract class SampleAbstractClass {
abstract public function sampleMethod();
}
インターフェースと違うのは、メソッドの宣言の前にもabstractが必要という点です。
抽象クラスを適用させたいときは、普通のクラスと同じくextendsで継承させます。
<?php
abstract class SampleAbstractClass {
abstract public function sampleMethod();
}
class B extends SampleAbstractClass {
public function sampleMethod(){
}
}
インターフェース同様、抽象クラス内で宣言された抽象メソッドが適用された先で実装されていないとその時点でエラーとなります。
逆にインターフェースと違う点としては、PHPが多重継承できない言語であることからもわかる通り、列挙で複数同時に適用させることはできないということです。
また、extendsを用いているので、抽象クラス内で宣言する際のアクセス修飾子はpublicとprotectedが使えます。
他に違いとしては、抽象クラスの中においては抽象メソッドではないプロパティやメソッドを実装することができるということです。
<?php
abstract class SampleAbstractClass {
abstract public function sampleMethod();
public $property = 1;
public function testMethod(){
echo $this->property;
}
}
class C extends SampleAbstractClass {
public function sampleMethod(){
echo 2;
}
}
$c = new C();
$c->testMethod();
上記のコードを実行すると1が出力されます。つまり、抽象クラス内の抽象メソッドではないメソッドは継承先でオーバーライドすることなく普通に利用することができるということです。
ではそれぞれの使い所は
以上のことから、インターフェースと抽象クラスは非常に似通っていることがわかります。
主な相違点としては、
・インターフェースは実体のあるメソッドを宣言することが出来ず、抽象クラスでは出来る
・インターフェースではメソッドの宣言しか出来ず、抽象クラスでは抽象ではないプロパティやメソッドを定義することが出来る
・インターフェースは複数同時に適用させることが出来、抽象クラスでは出来ない
が挙げられます。
これから、
抽象クラスは、共通の挙動とそれぞれの違う挙動を持つインスタンスを複数作るときに役立つ
ということがわかります。
また、そこから考えて
インターフェースはインスタンスを複数作るときに最低限実装すべきメソッドを保証するのに役立つ
と言えます。
例えばStrategyパターンのように、インスタンスの中身には関与せずにメソッドの呼び出し方のみ統一しようとした場合、想定しているメソッドがあったり無かったりするとややこしくなってしまうので、そういうときはインターフェースが有効となります。
とはいえ、Strategyパターンにおける同様の役割は抽象クラスでも果たせます。
まとめ
インターフェースは最低限の実装の明示や保証で、抽象クラスは共通部と差異の明示
という、挙動の違いというより使用意図の違いがあるということがわかりました。