id:anatooのブログ RSSフィード

 | 

2008年5月17日

PHPにはインターフェイスというものがありますよ、という話

なにかとPerl、Python、Ruby、JavaScript等の言語と比べて機能の不足を言及されることの多いPHPですが、

PHPには逆にこれらの言語にはない機能がある、それはインターフェイスだ、という話です。

このインターフェイスという奴は、タイプヒンティングと共に、PHPという言語がさっき挙げたような他の軽量言語とは違ったプログラミングスタイルを持っていることを象徴しています。


インターフェイスって何?

JavaやAS3なんかをやっている人だとわかるかと思いますが、有体にいえばinterfaceとはメソッドの宣言を集めたものです。

PHPマニュアルから説明を引用すると

インターフェイスにより、あるクラスが実装する必要があるメソッドの 種類を、

これらのメソッドの実体を定義することなく、指定するコードを作成できるようになります。

(PHP: オブジェクト インターフェイス - Manual)

コードで言うとこういうやつです。

<?php
interface Car
{
    public function run();
    public function stop();
}

インターフェイスはimplements演算子で実装できます。

Carインターフェイスを実装したMiraクラスは以下のようになります。

<?php
class Mira implements Car
{
    public function run()
    {
        // 何らかの処理
    }
    public function stop()
    {
        // 何らかの処理
    }
}

implementsしたインターフェイスのメソッド宣言をきちんと真似しないと、PHPがエラーを出してくれます。


これ何が嬉しいの?

はじめて見た人にとっては多分意味がわからないと思います。

僕は初めてJavaでインターフェイスに出会ったときこういう疑問を抱きました。

  • 「なぜわざわざ同じメソッド宣言を繰り返す必要が?」
  • 「機能を実装しているクラスだけでいいのでは?」

これは全くその通りです。

事実、オブジェクト指向においてインターフェイスという機能がなくてもプログラムはいくらでもかけます。

が、インターフェイスを使うことで得られるメリットがあります。


例えばさっきのCarインターフェイスを使うコードの場合。

<?php
// Carインターフェイスを通じて何らかの処理を遂行する関数procedure
function procedure(Car $somecar)
{
    $somecar->run();
    // 何らかの処理
    $somecar->stop();
}

このprocedure関数では引数$somecarの部分でタイプヒンティングしています。

引数として渡された$somecarは、Carインターフェイスを実装したクラスのインスタンスであることが保障されています。


もしprocedure関数に文字列を渡して呼び出した場合、きちんとPHPが以下のようにエラーを出してくれます。

Argument 1 passed to procedure() must implement interface Car, string given, called in ...

インターフェイスを使わずに実装クラスでタイプヒンティングすればいいのでは?

わざわざインターフェイスを使わずとも上記の例ではMiraクラスでタイプヒンティングすればいいではないか、と思う人もいるかもしれません。

<?php
function procedure(Mira $mira)
{
    $mira->run();
    // 何らかの処理
    $mira->stop();
}

これでも良いんじゃないか、ということですが、引数にMiraクラスのインスタンスのみしか受け付けないのならこれでも良いのです。

しかし、これではprocedure関数はMiraクラスの実装に依存する形になってしまいます。

対して、インターフェイスをタイプヒンティングすれば、実装を交換できます。


実装の交換?

<?php
class Copen implements Car
{
    public function run()
    {
        // ...
    }
    public function stop()
    {
        // ...
    }
}

例えば新しくCarインターフェイスを実装したCopenクラスを作ります。

Carインターフェイスでタイプヒンティングするのであれば、このクラスのインスタンスは、procedure関数に渡すことができます。

Miraクラスでタイプヒンティングした場合はCopenクラスのインスタンスを引数に渡せません。

インターフェイスを使うことで特定のクラスの実装に依存しないタイプヒンティングが可能になります。

いわゆる機能(メソッド宣言)と実装の分離というやつです。

これにより型の安全性と柔軟さを同時に得ることができます。


他の軽量言語にインターフェイスがなく、PHPにだけある理由

インターフェイスを使うことは一長一短です。

というのも、インターフェイスを使った場合、コードの記述量は増えるわけで、簡潔な記述とは程遠いものになることがあります。

その代わり、上記のようにインターフェイスをタイプヒンティングすることで、自由にロジックの交換ができる上に、引数は型が保障されます。


PHPにインターフェイスがある理由とは、

簡潔な記述よりも、記述のわかりやすさや型の安全性を重視しているPHPの設計思想の表れなわけです。


インターフェイスのより具体的な例を知りたい方は

デザインパターン関連のドキュメントにたくさんあります。

リンクをいくつか挙げておきます。


最後に

なんかここ変じゃね?みたいなところがあったら突っ込みお願いします。


追記

一応言っておくと、ダックタイピングよりもインターフェイス使った方が良いよっていう話をしているわけではなくて、PHPにはこういうスタイルがあるよ、と言ってるだけです。

タイトルそのままのことを言ってます。

shimookashimooka 2008/05/18 10:06 保障重要!
いかに「保証」させて、余計な事を考えずに済むか?は結構重要と思います。

tt25tt25 2008/05/18 23:59 CarとMira(車の名前?)のような関係なら、Carを抽象クラスにした方がいいかも。Miraもその他の具象クラス(車種)も共通する部分が多いはずなので、ジェネリックな処理をCarに書いておけばいろいろと助かります。

インターフェイスはIteratorのように、期待する振る舞いは似たようなもんだけど実装が全然違う、という場合に真価を発揮します。配列をforeachするのとテキストファイルを行ごとにforeachするのとは、内部的には全然別ですが使うときは同じ感覚、みたいな。

PHPにはIteratorインターフェイスが用意されてるので、任意のクラスでimplementすれば

$foo=new FooClass(”some arg”);
foreach($foo as $key=>$value){
echo ”{$key} = {$value}”;
}
のようなことができて素敵です。組み込みクラスだとSimpleXMLあたりがこれを実装してたはず。

http://php.net/manual/ja/language.oop5.iterations.php

タイプヒンティングについては詳しくないのでノーコメントでw

anatooanatoo 2008/05/19 00:23 >tt25
すいませんサンプルコードなのでその辺適当に書いてます。
インターフェイスか抽象クラスか等のどういう設計をすべきかというのは、記事の本論とは外れてた(この記事はインターフェイスを紹介してるだけです)ので端折りました。
あとIteratorは同じ日の↓の記事でも使ってますね。
とはいっても直接使ってるのはIteratorAggregateですけど。

tt25tt25 2008/05/19 02:07 すいません気づきませんでした><

yorutrainyorutrain 2008/05/20 22:50 interfaceのメソッドを実装しなければならないということは「契約によるプログラミング」でも重要な位置にありますね。
呼び出し側から考えれば、インターフェイスによって公開されているメソッドを呼べばいい。というのは脳の健康によいです。

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。

 |