PHPで多重継承する方法

PHPでは言語仕様的に多重継承が許されていません。これは、メソッド名が重複した場合の処置など、多重継承が様々な問題を引き起こしやすいからです(C++でプログラムを書くとよくわかります)。とはいえ、どうしても多重継承をしたい場合というのもあります。そこで、PHPではインタフェースを使って擬似的に多重継承ができるようになっています(Javaでも同じ)。

(このエントリーはクラス、継承、抽象クラスなどについて最低限の知識のある人を対象としています)


以下、インタフェースを用いた擬似的多重継承の方法を説明します。

ClassAとClassB

まず、次のような二つのクラス、ClassAとClassBを考えます。

<?php
class ClassA {
	private $a;

	public function __construct($a){
		$this->a=$a;
	}

	public function getA(){
		return $this->a;
	}
}

class ClassB {
	private $b;

	public function __construct($b){
		$this->b=$b;
	}

	public function getB(){
		return $this->b;
	}
}
?>


この二つのクラスを継承したClassABを作りたい場合を考えてみましょう。↓のようにできれば良いのですが、これはできません。

<?php
class ClassAB extends ClassA, ClassB {
...
}
?>

ClassBのinterfaceを作り、実装する

次に、擬似的な多重継承のためにClassBのインタフェースを作ります。インタフェースとは、抽象メソッドしか持たない抽象クラスのようなものです(違いについては後記)。

<?php
interface InterfaceB {
	public function getB();
}
?>

「クラスを継承する」のに対して「インタフェースを実装する」と言います。クラスを多重継承することはできませんが、インタフェースは多重実装することができます(抽象メソッドしか持たない抽象クラスを多重継承することはできませんが、インタフェースは多重実装できるという意味で抽象メソッドしか持たない抽象クラスと異なります)。

インタフェースは抽象メソッドしか持たないことが保障されているので、メソッド名の重複が起こっても、その処理の方法を必ずプログラマが記述することになります。


ClassBはInterfaceBを実装して次のようにします(implements InterfaceBが増えただけ)。

<?php
class ClassB implements InterfaceB {
	private $b;

	public function __construct($b){
		$this->b=$b;
	}

	public function getB(){
		return $this->b;
	}
}
?>

ClassAB(ClassAとClassBを多重継承したクラス)

まず解説より先に多重継承のソースを示します。

<?php
class ClassAB extends ClassA implements InterfaceB {
	// 多重継承したいClassBのインスタンスを保持する。
	private $instanceB;

	public function __construct($a, $b){
		parent::__construct($a);

		// ClassBのインスタンスを、与えられた引数で生成。
		$this->instanceB=new ClassB($b);
	}

	// InterfaceB#getBメソッドを実装する必要がある。
	// 動作の中身は$instanceB(ClassB)に完全に任せてしまう。
	public function getB(){
		return $this->instanceB->getB();
	}
}
?>

やっていることは、

  • InterfaceBを実装
  • ClassBのインスタンスをprivateフィールド($instanceB)に保持
  • メソッドの中身は$instanceBに丸投げ

です。

以下、順に解説します。

InterfaceBを実装

ClassABにはInterfaceBを実装させます。インタフェースはいくつでも実装できるので、ClassAを継承していようがInterfaceBを実装することができます。

<?php
class ClassAB extends ClassA implements InterfaceB {
?>
ClassBのインスタンスをprivateフィールド($instanceB)に保持

次に、多重継承したいクラス(この場合ClassB)のインスタンスを保持するprivateフィールドを作成します。

<<?php
// 多重継承したいClassBのインスタンスを保持する。
private $instanceB;
?>
メソッドの中身は$instanceBに丸投げ

あとは、すべてを$instanceBに丸投げしてしまいます。例えば、ClassABで実装しなければならないgetBメソッドは

<?php
public function getB(){
	return $this->instanceB->getB();
}
?>

です。$instanceBに仕事をさせて、その結果を返しているだけです。この例ではInterfaceBが持っているメソッドが一つしかありませんが、もし仮に10個のメソッドがあれば、これと同じような丸投げの処理を10個書きます。


このようにすれば、InterfaceBのメソッドをすべて持つことを強制された、しかし、その動作はClassBによって決められたClassABを作ることができます。見かけ上は、ClassBを継承しているのと同じです*1

実行例

ClassABは次のようにして使えます。外から見ると、まるでClassBも継承されているようです。

<?php
$instanceAB=new ClassAB(100, 200);
print($instanceAB->getA()."\n"); // 100と表示
print($instanceAB->getB()."\n"); // 200と表示
?>

結論

このようにして、PHPでは擬似的な多重継承ができます。もちろん、ClassAとClassBを入れ替えても(InterfaceAを作ってそれを実装しても)問題ありません。

*1:厳密には、ClassABはClassAやInterfaceBとしては扱えるがClassBとしては扱えないという意味で違う。