三陸牡蠣復興支援プロジェクト「SAVE SANRIKU OYSTER」



インターフェイス

“インターフェイス(interface)”とは?

“インターフェイス(もしくはインタフェース)(interface)”という言葉は、直訳すると“境界面”になります。一般的にこの“境界面”は物(もの)と人の間にあるもので、物が人に対して提供するコミュニケートの手段の事です。相変わらず意味が解かりません。

ある物がある機能を持っていて、その機能を使うかどうかの決定権を人間様に委ねる場合、人間様は物がその機能を持っている事を知っている必要があります。が、その機能がどういった仕組みでその機能を実現しているかまでは知る必要がありません。物は人間様に、自分がある機能を備えているという事実を知らせ、実行手段を提供するだけでいいのです。で、この、物が人間様に機能の存在を知らせると共に提供する実行手段が“インターフェイス”です。人が物の機能を使う時、必要な知識は機能の名前と意味だけです。人は物が提供する“インターフェイス”によって、その物が持つ機能の名前と意味を理解し、その機能を使えるようになります。“インターフェイス”は“機能の名前”といえます。ちなみに、物が“インターフェイス”によって機能の意味を人に知らせる行為(?)を“機能の意味をアフォードする”と言ったりもします。

身近なものだと、テレビのリモコンの操作ボタンが“インターフェイス”です。人間様はボタンの存在と意味を認知さえすればよく、電子工学?を理解していないとチャンネルを変えられないという事態にはなりません。

また、“テレビのリモコン”が“テレビのリモコン”でありうる為には、“テレビのリモコン”として存在するその物体が、“テレビのリモコン”でありうる為の機能を備えていて、同時に、“テレビのリモコン”でありうる為の“インターフェイス”を備えている必要があります。でなければその物体は“テレビのリモコン”であるとは言えません。テレビのチャンネルを換える機能を持たない“テレビのリモコン”はありえません。物には備えるべき機能があり、それを備えていなければ存在が認められません。

物は“インターフェイス”によって、自分が持つ機能の実行手段を提供するだけでなく、自分の状態を知らせたりもします(“バッテリー切れ”の様な)。つまり、人と人が言葉や仕草、表情等で行うように、物と人は“インターフェイス”によってコミュニケーションを行っているわけです。

物は人の様に、余程不良品でもない限り、嘘をついたり暴走したりはしません。が、人の勘違いによって予期せぬ問題が起こる可能性があります。この問題を回避するために、物と人がより正確にコミュニケート出来るように“インターフェイス”を設計しなくてはなりません。便利な機能も大事ですが、それを正確に扱えるような“インターフェイス”も大事なんです。物凄く便利な機能を謳っていても、それを使いこなせる技術が前提では、その機能が無いのと一緒です。操作がややこしくて通話が出来ない携帯電話は、通話機能が無いのと一緒です。なんだそりゃ。つーか話逸れ気味。

「interface(インターフェイス)」と「implements(実装)」

“インターフェイス”は“機能の名前”だなんて書きましたが、PHPにおける「インターフェイス」も同様に“機能の名前”です。では“機能の名前”の“機能”って何の事なのか?それはオブジェクトが提供する「パブリック」なメソッドの事です。オブジェクトを扱う時、使用するのはクラス外に公開される「パブリック」なメソッドのみで、「プライベート」又は「プロテクテッド」なメソッドは、オブジェクト内部でのみ使用されるため、その存在を意識する必要はありません。つまり、オブジェクトが提供する「パブリック」なメソッドが、オブジェクトが提供する“機能”です。

更に、オブジェクトはある役目を持っていて、その役目を果たすには“備えているべき機能”を備えている必要があります。つまり、“テレビのリモコン”でありうる為には“テレビのリモコン”でありうる為の“備えているべき機能”を備えていなければならない様に、ある役目を持ったオブジェクトも、そのオブジェクトでありうる為の“備えているべき機能”つまり“備えているべきメソッド”を備えていなければなりません。オブジェクトが“備えているべきメソッド”は1つとは限りません。“備えているべきメソッド”が複数である場合が殆どです。1つでも“備えているべきメソッド”を備えていないと、そのオブジェクトはそのオブジェクトではありえません。全て備えて初めて、そのオブジェクトでありうるのです。

要するに、「インターフェイス」は単なる“機能(メソッド)の名前”ではなく、“備えているべき機能(メソッド)の名前”であり、更に“備えているべきメソッド”は複数から構成されていて、その全てをひっくるめた全体の機能をいいます。“テレビのリモコン”は“テレビのリモコン”という機能を持っているから“テレビのリモコン”でありうるのです。

スクリプトを作り、その中でクラスを定義する時、行き当たりばったりで適当に定義する場合もあれば、ちゃんと仕様を決めてから定義する場合もあると思いますが、仕様を決めてから定義する場合、定義するクラスが何の為のクラスでどのような機能を持っているのか、作り始める前に決定しているはずです。仕様決定の段階では、どのような機能を持つのかだけ決めればよく、構造まで決める必要はありません。逆に、仕様で決めた機能を搭載し忘れたらダメです。

で、具体的にPHPではどのようなものなのか?クラスの定義に近いものですが、こちらは「interface」で定義し、更に「抽象メソッド」だけを定義します。これは殆ど「抽象メソッド」だけを備えた「抽象クラス」と同じです。「インターフェイス」は「抽象メソッド」しか定義できないので、「抽象メソッド」の定義時に使用する「abstract修飾子」は省略出来ます(省略してもしなくても「抽象メソッド」になる)。

「インターフェイス」の定義方法はクラスの定義方法と殆ど同じですが、この「インターフェイス」は「抽象クラス」みたいなもので、これのインスタンスを生成するというものではなく、継承した別のクラスがインスタンスを生成出来ます。(え?)、「インターフェイス」はクラスの様に「extends」を使って“継承”されるのではなく、「implements」を使って“実装”されます。

“継承”と“実装”の違いは、“継承”の場合、基底クラスの「抽象メソッド」の処理部を実装しないと自身も「抽象クラス」になるだけですが、“実装”の場合は、「インターフェイス」に定義されたメソッドを全てオーバーライドして処理部を実装しなくてはなりません。実装漏れがあるとその時点で「E_ERROR」エラーを発します。また、“継承”は一度に1つのクラスからしか出来ませんが、“実装”の場合は「,(コンマ)」で区切って複数指定できます。

「インターフェイス」が備えるメソッドは全て「パブリック」に宣言しなくてはなりません。それ以外の可視性では「E_ERROR」エラーを発します(そもそも“インターフェイス”は外部に提供するものなので)。アクセス制限修飾子を省略した場合の可視性は「パブリック」なので、「インターフェイス」の定義ではアクセス制限修飾子の宣言は殆ど意味が無いので省略します。

「インターフェイス」はメンバ変数を持つ事が出来ません(“インターフェイス”の意味を考えれば、処理内部で使われるプロパティを定義する必要が無いのが解かる。「インターフェイス」はクラスの様に“オブジェクトの構造を定義するもの”ではなく、“機能の形態を定義するもの”です)が、クラス定数(「インターフェイス定数」と呼ぶ事にする)は定義することが出来ます。

「インターフェイス定数」は、それを所有する「インターフェイス」があるクラスに実装された場合、身分的(?)にそのクラスで定義されたクラス定数となります。つまり、実装した「インターフェイス」が持つ「インターフェイス定数」と同じ名前のクラス定数を定義出来ません(定数の再定義となって「E_ERROR」エラーを発します)。

“継承”と“実装”は同時に行う事が出来ますが、その場合、「class 派生クラス名 extends 基底クラス名 implements インターフェイス名」と、“継承”が“実装”の左側に来るようにします。逆にするとシンタックスエラーになります。

これまた論よりコード。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    interface iHODE{
        /* protected $property; */
        const iHODE_CONST = "HODENASU";
        abstract public function ihode_method();
        /* abstract protected function protected_method(); */
        /*
        public function not_interface_method(){
            //
        }
        */
    }
    
    #極力シンプルに(「abstract」は省略出来る)
    interface iNDA{
        function inda_method_1();
        function inda_method_2($arg1, $arg2 = "");
        static function inda_method_3();
    }
    
    #「インターフェイス」の実装漏れがある
    /*
    class NDA implements iNDA{
        public function inda_method_1(){
            //
        }
    }
    */
    
    class GASU{
        const GASU_CONST = "GASUKA";
    }
    
    #複数の「インターフェイス」を“実装”する(ついでにクラスの“継承”も)
    class HODE extends GASU implements iHODE, iNDA{
        #「クラス定数」と「インターフェイス定数」を再定義してみる
        /* const iHODE_CONST = "HODENASU"; */
        const GASU_CONST = "HODEGASUKA";
        public function __construct(){
            echo "self::GASU_CONST    : ", self::GASU_CONST, "\n";
            echo "parent::GASU_CONST  : ", parent::GASU_CONST, "\n";
            echo "GASU::GASU_CONST    : ", GASU::GASU_CONST, "\n";
            echo "self::iHODE_CONST   : ", self::iHODE_CONST, "\n";
            echo "parent::iHODE_CONST : "/*, parent::iHODE_CONST*/, "\n";
            echo "iHODE::iHODE_CONST  : ", iHODE::iHODE_CONST, "\n";
        }
        public function ihode_method(){
            //
        }
        public function inda_method_1(){
            //
        }
        public function inda_method_2($a1, $a2 = "hode"){
            //
        }
        static public function inda_method_3(){
            //
        }
        private function hode_method(){
            //
        }
    }
    
    new HODE;
?>
</pre>
Fatal error:「インターフェイス」ではプロパティを定義出来ない)
(Fatal error:「パブリック」以外の可視性は不可)
(Fatal error:「インターフェイス」で通常のメソッド定義は出来ない)
(Fatal error:「インターフェイス」の実装漏れがある)
(Fatal error「インターフェイス定数」を「クラス定数」として再定義出来ない)
self::GASU_CONST    : HODEGASUKA
parent::GASU_CONST  : GASUKA
GASU::GASU_CONST    : GASUKA
self::iHODE_CONST   : HODENASU
parent::iHODE_CONST : (Fatal error:未定義のクラス定数)
iHODE::iHODE_CONST  : HODENASU

例によって、コメントアウトしている所で「E_ERROR」エラーが発生します。

interface iHODE{ 定義部 }」の部分で、「iHODE」という名前の「インターフェイス」を定義しています。クラスの定義と似ていますが、定義部で定義できるのは、インターフェイス定数(クラス定数の様なもの)と抽象メソッドのみです。定義出来るメソッドが抽象メソッドのみなので、「abstract修飾子」が省略出来ます。また、可視性は特性上常に「パブリック」です。スタティックメソッドの定義も可能。

「インターフェイス」は、クラスに“継承”ではなく“実装”されます。「implements」を使って“実装”され、複数の「インターフェイス」が指定できます。クラスは、全てのメソッドをオーバーライドして実装しなくてはなりません。

「インターフェイス」とそれを実装したクラスは、継承関係にあるわけではないので、インターフェイス定数を「parent::iHODE_CONST」で参照する事は出来ません。

ある役目を持ったクラスを定義する時、クラスに与えるその役目を果たす為の第一条件は、“クラスが役目を果たす為の機能を備えている事”です(“テレビのリモコン”は“テレビのリモコンとしての機能”を備えているから“テレビのリモコン”でありうる様に)。どうやって各機能を実現するかよりも、役目を果たす為の機能を全て備えている事がまず大事です。具体的な処理部の実装とは別問題として考える為に態々「インターフェイス」を定義します。

ところで、“物と人がより正確にコミュニケート出来るように「インターフェイス」を設計しなくてはならない”なんて書きましたが、PHPにとっての「インターフェイス」はどのように意識して設計すればよいのでしょうか?それは“判り易い名前を付ける事”です(それだけ?)。PHPのビルトインクラスのメソッドや、PEARにて配布されているクラスのメソッドの名前は、「getXxxYyy()」や「setXxxYyy()」、「createXxxYyy()」のように、意味的に共通するメソッド名は共通する接頭辞なんかを付けたりしています。これらのコードを参考にして、メソッドのネーミングを行うと良いです。

インターフェイスの拡張

「インターフェイス」は「extends」を使って、他の「インターフェイス」を継承して拡張する事が出来ます。継承する場合、「,(コンマ)」で区切って複数指定できます。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    interface iHODE{
        const iHODE_CONST = "HODE";
        function ihode_method();
    }
    
    interface iNASU{
        const iNASU_CONST = "NASU";
        function inasu_method();
    }
    
    #「インターフェイス」を継承する
    interface iHODENASU extends iHODE, iNASU{
        /* const iHODE_CONST = "HODENASU"; */
    }
    
    #「インターフェイス」を実装する1
    class HODENASU1 implements iHODE, iNASU{
        public function ihode_method(){
            //
        }
        public function inasu_method(){
            //
        }
    }
    
    #「インターフェイス」を実装する2
    class HODENASU2 implements iHODENASU{
        public function ihode_method(){
            //
        }
        public function inasu_method(){
            //
        }
    }
?>
</pre>
Fatal error:継承元の「インターフェイス定数」を再定義出来ない)

インターフェイス「iHODENASU」に、インターフェイス「iHODE」とインターフェイス「iNASU」を継承させました。結果的に「HODENASU1」クラスと「HODENASU2」クラスは全く同じ構造です。

更に、以下のサンプルと結果をご覧下さい。

<pre>
<?php
    #「インターフェイス」の定義
    interface iBASE{
        //
    }
    interface iSUB extends iBASE{
        //
    }
    
    #「インターフェイス」の実装
    abstract class BASE implements iBASE{
        //
    }
    class HODE extends BASE{
        //
    }
    class NASU implements iSUB{
        //
    }
    
    #オブジェクトが実装する「インターフェイス」を調べる
    $imp_arr["HODE"] = class_implements(new HODE);
    $imp_arr["NASU"] = class_implements(new NASU);
    
    print_r($imp_arr);
?>
</pre>
Array
(
    [HODE] => Array
        (
            [iBASE] => iBASE
        )

    [NASU] => Array
        (
            [iSUB] => iSUB
            [iBASE] => iBASE
        )
)

class_implements()」関数は、「指定したオブジェクトが実装するインターフェイス名を配列に格納して返す」関数です。

サンプルの結果を見ると、何れのクラスも「iBASE」インターフェイスを実装しているのが判ります。“ある別の「インターフェイス」を実装したクラス”を継承したクラスも、“ある別の「インターフェイス」を継承した「インターフェイス」”を実装したクラスも、そのクラスは継承元の「インターフェイス」を実装していると見なされます。

オブジェクト引数のインターフェイス指定

関数やクラスのメンバ関数の定義時に“オブジェクト引数のクラス指定(「オブジェクト引数のクラス指定」のページで解説)”が出来ますが、同様に「インターフェイス名」も指定することが出来ます。この場合、渡されたオブジェクトが、指定された「インターフェイス」を実装しているか否かがチェックされます。

<pre>
<?php
    interface iHODE{}
    interface iNASU{}
    interface iHODENASU extends iHODE, iNASU{}
    interface iNDA{}
    
    abstract class HODENASU implements iHODENASU{}
    class APETOPE extends HODENASU{}
    
    function hodenasu_func(HODENASU $obj){
        echo '$obj must be an instance of \'HODENASU\'.', "\n";
        echo '$obj is an instance of \'', get_class($obj), "'.\n\n";
    }
    function ihode_func(iHODE $obj){
        echo '$obj must implement interface \'iHODE\'.', "\n";
        echo '$obj implements...', "\n";
        print_r(class_implements($obj));
    }
    function inda_func(iNDA $obj){
        echo '$obj must implement interface \'iNDA\'.', "\n";
        echo '$obj implements...', "\n";
        print_r(class_implements($obj));
    }
    
    hodenasu_func(new APETOPE);
    ihode_func(new APETOPE);
    /* inda_func(new APETOPE); */
?>
</pre>
$obj must be an instance of 'HODENASU'.
$obj is an instance of 'APETOPE'.

$obj must implement interface 'iHODE'.
$obj implements...
Array
(
    [iHODE] => iHODE
    [iNASU] => iNASU
    [iHODENASU] => iHODENASU
)
(Fatal error:インターフェイス「iNDA」を実装していなければならない)

単純に、渡されたオブジェクトが指定した「インターフェイス」を実装していればOKで、実装していなければ「E_ERROR」エラーを発します。

定義済みのインターフェイス

PHPには予め幾つかの「インターフェイス」が定義されています。

定義済みの「インターフェイス」にはどのようなものがあるのか、下記のスクリプトを実行して見てみます。

<pre>
<?php
    #定義済み「インターフェイス」と定義されるメソッド名を取得・出力する
    foreach(get_declared_interfaces() as $interface_name)
        $interfaces[$interface_name] = get_class_methods($interface_name);
    
    print_r($interfaces);
    
    echo "<hr />";
    
    #定義済み「インターフェイス」を実装する定義済みクラス名を抽出・出力する
    foreach(get_declared_classes() as $class_name){
        $buf_arr = array();
        $class_abj = new ReflectionClass($class_name);
        $imp_arr = $class_abj->getInterfaces();
        foreach($imp_arr as $imp_obj){
            $imp_arr_ = get_object_vars($imp_obj);
            $buf_arr[] = $imp_arr_["name"];
        }
        if(count($buf_arr))
            $arr[$class_name] = $buf_arr;
    }
    
    print_r($arr);
?>
</pre>
Array
(
    [Traversable] => Array
        (
        )

    [IteratorAggregate] => Array
        (
            [0] => getIterator
        )

    [Iterator] => Array
        (
            [0] => current
            [1] => next
            [2] => key
            [3] => valid
            [4] => rewind
        )

    [ArrayAccess] => Array
        (
            [0] => offsetExists
            [1] => offsetGet
            [2] => offsetSet
            [3] => offsetUnset
        )

    [Reflector] => Array
        (
            [0] => export
            [1] => __toString
        )

    [RecursiveIterator] => Array
        (
            [0] => hasChildren
            [1] => getChildren
            [2] => current
            [3] => next
            [4] => key
            [5] => valid
            [6] => rewind
        )

    [SeekableIterator] => Array
        (
            [0] => seek
            [1] => current
            [2] => next
            [3] => key
            [4] => valid
            [5] => rewind
        )

)

Array ( [ReflectionFunction] => Array ( [0] => Reflector ) [ReflectionParameter] => Array ( [0] => Reflector ) …省略 [RecursiveIteratorIterator] => Array ( [0] => Iterator [1] => Traversable ) [FilterIterator] => Array ( [0] => Iterator [1] => Traversable ) …省略 [SimpleXMLIterator] => Array ( [0] => Traversable [1] => RecursiveIterator [2] => Iterator ) )

定義済みの「インターフェイス」と定義されるメソッド名を出力しました。今の所(PHP5.0.3)この7つが定義されています。

ついでに、定義済みの「インターフェイス」を実装する定義済みクラス名を抽出、出力しました。

今回のサンプルで取得できた、現在定義されている7つの「インターフェイス」は、「イテレーション」と「リフレクション」で扱います。ちなみに、サンプル中の「ReflectionClass」クラスも「リフレクション」で扱います。詳細については各ページで解説します。

作成日:2005年01月13日 最終更新日:2005年01月13日
【印刷モード風モード で表示】