PHP7では無名クラスが導入されます。
これ、単に構文だけの話にせず、「クラスの第3の能力が単独で切り出された」と捉えると面白いのかなと思いました。
クラスが持つ3つの役割
「コーディングを支える技術」の西尾泰和氏によると、クラスには
- ユーザー定義型
- 実装の再利用の単位
- インスタンス生成器
の3つの役割があるといいます。 クラスが扱いづらい概念だとすれば、それはこの3つの能力をまとめて持っているからです。
PHPのユーザー定義型、インターフェース
PHPの場合、タイプヒントがありますし、定義したクラスは後から拡張することができません(オープンクラスではない)。
if ($obj instanceof FooClass) {
//...
}
この「$objがFooClassのインスタンスである」という事実だけで、持っているメソッドは確定しており、その性質は後から変更されません。つまりPHPのクラスにはユーザー定義型としての能力があります。
このユーザー定義型の能力のみを持ち、インスタンス生成能力も、実装も持たない概念が切り出されました。それがインターフェースです。
if ($obj instanceof FooInterface) {
//...
}
PHP7では戻り値のタイプヒントも解放されますので、以前よりは詳細に型を記述できるようになるでしょう。動的型付けなので実行時型検査しかできませんが、テストがカバーできない範囲の検査が可能になるので、よりプログラムを堅牢にすることができるでしょう。
PHPの実装再利用の単位、トレイト
クラスは継承して実装を再利用することができます。しかしクラスにはユーザー定義型の能力がもれなく付随するので、継承すると色々と副作用が出てしまいます。PHPはJavaに倣って、多重継承は禁止することになってしまいました。
クラスには2つの相反する役割があります。1つ目は「インスタンスを作るためのもの」という役割で、このためには「完結した、必要なものを全部持った、大きなクラス」である必要があります。2つ目は「再利用の単位」という役割で、このためには「機能ごとの、余計なものを持っていない、小さなクラス」である必要があります。
クラスが「インスタンスを作るためのもの」として使われているときには、再利用の単位としては大きすぎるのです。
(コーディングを支える技術 p.241)
PHP5.4から導入されたトレイトは再利用の単位として切り出された概念です。
実装を持ちますが、インスタンスの生成能力はありませんし、ユーザー定義型にもなり得ません。 型としての能力がないのは、クラスにミックスインする際に、メソッド名を変えるなど好き勝手にできるからです。あるトレイトをuseしているからと言って、そのクラスに特定のメソッドが存在するとは保証されません。
trait FooTrait
{
function doSomething() {
echo __METHOD__;
}
}
class Foo {
use FooTrait {
doSomething as fooSomething;
}
}
無名クラス記法によるインスタンス生成
PHP7の無名クラス記法があれば、トレイトからその場でインスタンスを生成することができます。型が必要であればインターフェースをその場でimplements指定すればいいだけ。
$obj = new class() implements FooInterface {
use Foo, Baa;
};
クラスをnewする場合に比べて記法が冗長ですが、そもそもnewをあちこちに書くのはテスタビリティを下げますので、DIコンテナやサービスロケーターで書くべきでしょう。
クラスと言う概念が希薄化し、使い捨てるものになります。
クラス定義を使わずにOOPしてみる
要するに、PHP7ではクラスを定義せずにオブジェクト指向プログラミングができることになります。
- 実装の再利用がしたいならばトレイトを定義して使いまわす。
- 型を定義して事前/事後条件を表現したいならインターフェースを使う。
- インスタンスの組み立てはDIコンテナ内で無名クラス記法を使う。
感想
まだ特に実践的なコードは書いていないのであくまで想像ですが、いろいろ変わるなあと。
- DIコンテナの能力増大。その場で設定できる範囲がすごく増える。
- 再利用できない実装がなくなる。
- 再利用できない型がなくなる。
- デザインパターンに囚われないソースコード分割が可能になる。
- 今までのデザインパターンが使えないので途方に暮れる。
柔軟さも上がるけれど、型記述をきちんとすれば堅牢さも上げられると思います。
その昔gotoという一つの概念だったものがwhileやbreakや例外機構というもっと具体的な機能に分割されたように、クラスの概念も一つでは扱いづらく不便だったので、分割されたと理解するのがよいと思います。
PHP7時代のライブラリには、クラスを用意せず、トレイト、インターフェース、簡単なDIコンテナ+デフォルト設定だけ準備して、後は好きにカスタマイズして使いなよ、というスタンスのものが現れるかも知れませんね。この辺先進的な他言語の皆様はどうしているのか気になるところです。
『クラスを定義するとか古い』『クラス厨は老害』そんな風に言われる時代が来たら面白いですね。