連載
» 2015年11月04日 05時00分 UPDATE

PHPオブジェクト指向プログラミング入門(5):PHPにおけるインターフェースと抽象クラス、多重継承、トレイトの使い方 (1/2)

「PHPで、どのようにオブジェクト指向プログラミングをしていくか」を解説する連載。今回は、PHPにおけるインターフェースと抽象クラスの概要や使い方、両者の違い、多重継承、トレイトについて解説します。

[古庄道明,株式会社格子組]
「PHPオブジェクト指向プログラミング入門」のインデックス

連載目次

 「PHPで、どのようにオブジェクト指向プログラミングをしていくか」を解説する本連載。今までは、下記のように、PHPを例とした、オブジェクト指向について学んできました。

 前回の「便利だけど使いどころが難しいPHPの代表的なマジックメソッドと無名関数の使い方」では、PHPに固有(に近い)仕様である、「マジックメソッド」について説明しました。今回は、再び「比較的どの言語においても通用しやすい」お話である、インターフェースと抽象クラス、および多重継承についてあらためて学んでいきましょう。

「インターフェース」とは何か?

 インターフェースは英語で「interface」と書きますが、英語本来の意味としては「二者間の接点/接続面」「接触や連絡」といった意味です。

 「ユーザーインターフェース(UI)」という言葉は割と一般的に使われているように思いますが、これは「コンピューターとユーザーという二者の間にある、情報をやりとりするための決まりごと(接点)」というような意味です。

 OOP(object-oriented programming:オブジェクト指向プログラミング)におけるインターフェースは、おおむね「そのインスタンスは必ず、このメソッドを持っていることを約束します」という感じで使われることが多いです。

PHPにおけるインターフェースの宣言

 まずは実装を確認してみましょう。PHPにおけるインターフェースの宣言はリスト1のような形式で書きます。

<?php
//
interface hoge {
    public function t1();
}
リスト1

 インターフェースで記述できるのは「publicなfunctionのみ」です。プロパティは書けませんし、メソッドにしても、protectedやprivateなものは書くことができません。また、「実装」を書くことはできず、あくまでも「このメソッドがあるよ」という「宣言」のみの記述です。

インターフェースを使うときは、「implements」を使う

 インターフェースを使うときは、クラスの継承と同じように記述しますが、「extends」ではなく、「implements」を使います。

<?php
//
interface hoge {
    public function t1();
}
class foo implements hoge {
    public function t1() {
        echo "foo's t1()\n";
    }
}
//
$obj = new foo();
$obj->t1();
リスト2

「抽象クラス」とは何か?

 インターフェースのお話は、いったん置いておきまして、続いて「抽象クラス」を見ていきましょう。

 抽象クラスは「抽象メソッドを含むクラス」です。抽象クラスを、interfaceと同じような内容で実装してみましょう。

PHPにおける抽象クラスの宣言

 PHPにおける抽象クラスの宣言は、リスト3のような形式で書きます。

<?php
//
abstract class hoge {
    abstract public function t1();
}
リスト3

 抽象クラスは「クラスの一種」なので、プロパティを含めることができます。また、抽象メソッドにおいては、publicのみではなくprotectedなメソッドに対して「abstract」をマークすることも可能です。ただし「abstract private」は記述できません。

<?php
//
abstract class hoge {
    abstract public function t1();
    abstract protected function t2();
//
private $i_;
}
リスト4

抽象クラスを使うときは、「extends」を使う

 抽象クラスは、これまでの連載で紹介した「継承」と同様に「extends」キーワードで利用が可能です。

<?php
//
abstract class hoge {
    abstract public function t1();
    abstract protected function t2();
//
private $i_;
}
class foo extends hoge {
    public function t1() {
        echo "foo's t1()\n";
        $this->t2();
    }
    protected function t2() {
        echo "foo's t2()\n";
    }
}
//
$obj = new foo();
$obj->t1();
リスト5

インターフェースと抽象クラスの比較:実装面から

 インターフェースと抽象クラスは、大変に「似ている」部分があります。まずはインターフェースと抽象クラスを、実装面から比較していきましょう。

 どちらも「単体ではnewできない」という大きな制約があります。実際に試してみましょう。

<?php
//
interface hoge {
    public function t1();
}
//
abstract class foo {
    abstract public function t1();
}
//
$obj = new hoge(); // Fatal error: Cannot instantiate interface hoge
$obj = new foo(); // Fatal error: Cannot instantiate abstract class foo
リスト6(インターフェースと抽象クラスの宣言とコンストラクターの呼び出し)

 4行目にあるようにinterfaceで宣言できるのは「public function」のみです。また「部分的に実装をする」ようなことはできず、全て「宣言のみ」です。一方で抽象クラスは「通常のクラスで記述可能なあらゆるもの」が記述できるので部分的に実装することも可能なのと、抽象メソッドもpublic以外にprotectedが指定できます。

<?php
//
interface hoge {
    public function t1();
    protected function t2(); // Fatal error: Access type for interface method hoge::t2() must be omitted
    public function t3() { // Fatal error: Interface function hoge::t3() cannot contain body
    }
//
private $i_; // Fatal error: Interfaces may not include member variables
}
リスト7(インターフェースの宣言における制約の例)
<?php
//
abstract class hoge {
    abstract public function t1();
    abstract protected function t2();
    public function t3() {
    }
private $i_;
}
リスト8(抽象クラスではリスト7のような制限がない)

 このように、実装の面から見ると「インターフェースでできることは全て抽象クラスでもできる(後述しますが、インターフェースにしかできないことも実際にはあります)」一方で、「抽象クラスでできるが、インターフェースではできないこと」があります。

 これだけを見ると「では抽象クラスばかりが使えて、インターフェースは実際には使われないのではないか?」という疑問が出てくると思います。では、本当に「インターフェースは実際には不要なもの」なのでしょうか?

インターフェースと抽象クラスの比較:本来の違い

 インターフェースと抽象クラスを、実装ではなく「本来の目的」で見ると、その違いが見えてきます。

 抽象クラスは本質的に「クラス」であり、その目的は「継承」です。抽象メソッドは「自分では実装しない(できない)けど、子クラスで実装しておいてください」という継承先での実装の制約ですが、同時に「親クラスで実装可能なものは実装しておきます」という、「実装の継承」を想定しているケースも多いです。

 一方で、インターフェースが本質的に持っているのは「外部に対する保証と約束」です。

 「このインターフェースをimplementsしている場合、必ずこのメソッドが(publicで/外に開いて)存在していることを約束します」という感じです。プログラムにおいて「型」という概念は幅広い意味と定義を含んでいます。

 インスタンスの「型」が「このメソッド群を全て持っているかどうか?」という観点で考えられる場合(これを「ダックタイピング」と言います)、インターフェースは「型のみを定義できる記法である」と言うこともできるでしょう。

 このように、インターフェースと抽象クラスの本質は「実装の継承の一手段なのか」「型の継承なのか」という、本質的なところでは「大きく異なっている」ことが分かります。

 ただ、例えば典型的にはC++には「抽象のみで、インターフェースがない」ので、実装として「どちらを使うか」については、現場や今までの技術経歴によっても、大分と異なっているようです。

 とはいえ、PHPではどちらも実装があるので、「実装の継承」であれば抽象クラスが、「型の継承(とメソッドの保証/約束)」であればインターフェースが、それぞれ「より適している」ので、そのように使うと食い違いが減るのではないか、と思います。

       1|2 次のページへ

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。