【PHP】抽象クラスとインターフェースとは?それぞれのメリット・違いを比較
JavaやPHPなど、オブジェクト指向をもとに作られたプログラミング言語の多くは、「クラス」というデータの集合体を提供しています。
これはユーザが定義できる機能の一つで、様々なプロパティやメソッドを持たせると、プログラム内で、細分化された一つの小さなプログラムを扱うような感覚で、処理を実装することが可能です。
詳しく話すと長くなるので、クラス(class)の機能について知りたい方は、PHPの公式リファレンスを参照してみてください。
classは単体でも十分開発を効率化してくれる機能になり得ますが、そのclassを更に扱いやすく、かつプログラムの整合性を高める機能として、PHPには抽象クラスとインターフェースという機能が存在します。
抽象クラスとインターフェースはPHPを学習する上で、ほぼ必ずと言っても過言ではない頻度で登場しますよね。
しかし、この2つの機能はよく似ており、説明を聞いても「何が違うの?」という疑問で次のステップに移ってしまう方も少なくないでしょう。
そこで今回は、PHPにおける抽象クラスとインターフェースの違いについて、それぞれのメリットを比較していくような形で、どういった違いがあるのかを解説していきます。
【前提】抽象クラス・インターフェースとは?
ここに辿り着いている方は、既に学習している方がほとんどだと思いますが、復習がてら、抽象クラスとインターフェースがどういったものなのか、改めて説明します。
この辺りは人によって説明する内容がしばし異なる部分でもあるので、差異を見る意味でも読んでみてください。
抽象クラス
抽象クラスは、中身のないメソッドを定義できるクラスのことです。
作成する場合は、以下のように、abstractという修飾子を付けてからクラスを宣言します。
abstract class AbstractClass
{
// 頭にabstractを付け、抽象メソッドを定義
abstract protected function getValue();
abstract protected function prefixValue($prefix);
// getValueで受け取った値を出力する
public function printOut()
{
print $this->getValue() . "\n";
}
}
抽象クラスの中でabstructを付けたメソッドが、抽象メソッドとして扱われます。
抽象クラスを使うのは、「具体的な処理を決めたくない場合」です。
「サブクラスの具体的な処理までは決まっていないが、イメージとして、ひとまずこういった処理は実行せたい」と考えるケースは珍しくありません。
似たような機能のサブクラスを有する場合は、サブクラスがどういったクラスなのかを具体的に定めるようなスーパークラスを作ると、その後の機能改修がやりにくくなってしまいます。
そういった場合、全てのサブクラスで共通して定義させたいメソッドなどを、先んじて定義しておくのです。
さすれば、「このクラス及びサブクラスではこのメソッドが必須ですよ」と、プログラム的にも人間的にも示すことが可能になりますし、直接的にサブクラスの具体性を示すことはなくなります。
インターフェース
インターフェースは抽象クラスにおける抽象メソッドと同様、具体的な処理が記載されていないメソッドの定義のみが行われるものです。
プロパティや通常のメソッドも定義・宣言できる抽象クラスと違い、インターフェースは空のメソッドの定義のみに対応しています。
<?php
// インターフェイス 'Template' を宣言する
interface Template
{
public function setVariable($name, $var);
public function getHtml($template);
}
// インターフェイスを実装する。
class WorkingTemplate implements Template
{
private $vars = [];
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
public function getHtml($template)
{
foreach($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
}
プロパティや通常のメソッドを定義できない点では抽象クラスに劣っているように見えますが、抽象クラスはあくまでクラスのため、一つのサブクラスにつき一つの抽象クラスだけしかスーパークラスになれません。
一方、インターフェースは下記のように記述することで、いくつものインターフェースを実装することができます。
class WorkingTemplate implements Template, Templete2, Templete3
{
// 処理
}
このように差別化されているため、それぞれ異なるメリット・デメリットが浮き上がってくるのです。
抽象クラスとインターフェースの共通点
抽象クラスとインターフェースはよく似ており、それぞれ共通した特徴をいくつか備えています。
まずは、それぞれどういった部分が似通っているのか、特筆すべき部分についてよく確認しておきましょう。
1.インスタンスに出来ない
抽象クラスやインターフェースの最大の特徴とも言えるのが、インスタンスに出来ないという点です。
インターフェースはそもそもクラスではないので当然ですが、抽象クラスをインスタンスにしようとしても、同様にエラーが発生します。
<?php
abstract class Sample
{
public function __construct()
{
echo "このコードは実行されません。";
}
}
$sample = new Sample();
// この処理は実行されない
echo "処理が完了しました。";
発生するエラー文
Fatal error: Uncaught Error: Cannot instantiate abstract class Sample in [ エラー発生箇所 ]
「Sampleっていうクラスは抽象クラスで、インスタンスには出来ないよ」とエラーが発生しているのがわかりますね。
抽象クラスやインターフェースは、あくまでも共通した約束事を持たせることが目的であって、実際に処理を実行する必要性はありません。
一般にクラスとは明確な差別化が図られている点、コードの可読性を上げる目的でも、抽象クラスやインターフェースの有効活用が求められるのです。
2.必要最低限の機能を定義する
先述のように、抽象クラスやインターフェースは主役ではなく、どちらかと言えばクラスに共通した約束事を持たせるための脇役です。
要するに、メインの存在ではないので、処理は比較的簡素なものにまとめるのが、基本的な活用方法になります。
通常のクラスであれば、プロパティからメソッドまで、実行したい処理を記述していきますが、抽象クラスやインターフェースでは、そういったコーディングは御法度です。
一応、abstractのクラスであってもプロパティや通常のメソッドは組めるもののコード上そういったことをするのは紛らわしいので、あまりする意味はないでしょう。
抽象クラス・インターフェースを比較する
では、本題に入りましょう。
抽象クラスとインターフェースは、曖昧な約束事を決めるものであり、インスタンスに出来ない点などは共通しています。
ただ、機能として分かれている以上は、差別化されている点もいくつかあるのです。
設計・開発思想による部分は大きいですが、ここではあくまで管理人が考える抽象クラスとインターフェースの違いをいくつか列挙していきます。
1.共通した行動を表せるインターフェース
インターフェースと抽象クラスの違いを語る際、それぞれどういった約束事を示すのか、というのは、ある程度決まっています。
例えば、「車」と「犬」がいたとしましょう。
どちらもそれぞれ異なる物ですが、広く抽象的に表すと「動くもの」と呼べます。
これをクラス関係で表すとき、「動くもの」クラスを抽象クラスとし、それを継承した車と犬をサブクラスにするのは、果たして正しいでしょうか。
動くものと言っても、地球上にあるほとんどの物は動いていますし、あまりにも範囲が広すぎます。
したがって、動くもの は 犬 である、というようなis-a関係を成り立たせることは難しいです。
ただし、それらの物に「動く」という行為が共通していないかというと、そうではないですよね。
例えば、人間は障害がある場合を除き、ほとんどの人が動くことの出来る生物であって、動く前提でない人間(動かないような進化をした人間)がチラホラ出てくるのは困ります。
そこで、インターフェースを上手く活用します。
犬だろうが車だろうが魚だろうが、動く前提であるべきものには全て「動くもの」というインターフェースを持たせる。
そうすれば、必ず動くべきものが動いている、破綻のない世界が出来上がります。
インターフェースは自由度が高い
先述したように、インターフェースは物事のカテゴリを横断して、それぞれに共通する動作・習性を定められます。
例えば、犬科を抽象クラスとして、犬・タヌキ・オオカミのサブクラスを生成したとして、オオカミとイヌのみに共通した習性をインターフェースで実装すれば、タヌキに影響を与えずに詳細を定義していくことができます。
このように、クラスに関係なく、「こういった処理をしておくもの」という約束事を自由に定められるのは、プログラミングにおいても重宝します。
例えば、入力された値に不正な値が入っていないかどうかを確認するような処理(バリデーション処理)を、必要なクラスごとに実装するよう共通化するとしましょう。
よく見る処理ですが、これを抽象クラスで継承させようとすると、一つの処理が原因で関連性のあるサブクラスをわざわざ別のクラスで分けて管理したり、サブクラスが不要なメソッドを継承するなどしてプログラムの整合性を低下させたり、といった問題が生じます。
インターフェースを実装することには、そういったクラスの縛りはなく、「値のチェックをお願いします。」と実装先のクラスに示しているだけなので、機能として取り回ししやすいのです。
2.具象のベースとなる抽象クラス
抽象クラスはインターフェースと違って自由度は低いですが、オブジェクト指向の中核を担う以上、具象クラスのベースになるという、切っても切れないメリットを持ち合わせています。
具象クラスとは、抽象クラスを含めたいくつかのクラスを経由し、より具体的でメインルーチンに関わるような処理の内容を持ったクラスのことです。
具象クラスのベースとなる点で言えば、インターフェースよりも、プロパティやデータまである程度定義しておける抽象クラスの方が、より強い制約・規則をサブクラスに強いることができます。
これは設計上の話でもそうですが、インターフェースが乱立する状態というのは、オブジェクト指向において良い状態とは言えません。
インターフェースそのものは処理を持たず、一つのインスタンスにもなれないため、プログラムの実行部分にほとんど関わらないためです。
一方で具象クラスは、メインルーチンで行う内容で細分化してカテゴライズした部品であり、これが乱立している状態は、設計がまとまっていれば美しくメンテナンス性も高いプログラムが仕上がります。
そういった設計に至るためにも、抽象クラスを有効活用し、以下にインターフェースへの依存度を減らしながらサブクラスの内容をある程度イメージできるか、よく考える必要があると言えるのです。
抽象クラスは利用できる機能が多い
抽象クラスは基本的にクラスとして立ち回れる都合上、
- プロパティの保持
- IDEによる管理が効率化
など、クラス特有の恩恵を受けられます。
また、Laravelなどのフレームワークを利用する場合、ファサードのような機能もクラスを基準にサポートされているため、自然と抽象クラスの出番も増えていきます。
だからといって抽象クラスばかりで共通化を狙うのは良くないですが、あくまでインターフェースはクラスの付随機能という点は、常に念頭に置いておくべきでしょう。
まとめ
抽象クラスとインターフェースは、クラスに約束事を設ける、という点で、各オブジェクトの関係性や機能の安定性を確保するために役立ちます。
ただ、抽象クラスはあくまでクラスであり、サブクラスとの関連性が明確に無ければならない一方で、インターフェースは機能単位の共通化など、付随する能力の共通化に向いているというように、それぞれ違いがあります。
オブジェクト指向プログラミングにおいても、抽象クラスとインターフェースの違いの理解を問われることはままあるため、初心者の方はよく理解しておいた方がよいでしょう。