PHPにおける「抽象クラス」「インタフェース」「無名クラス」Web業界で働くためのPHP入門(18)(2/2 ページ)

» 2018年05月30日 05時00分 公開
前のページへ 1|2       

インタフェース

 抽象クラスは普通のクラスに幾つか抽象メソッドが含まれた状態でした。つまり、普通のメソッドと抽象メソッドが混在しています。これを、抽象メソッドのみにすると、インタフェースとなります。そのインタフェースを次に見ていきましょう。

抽象メソッドばかりのインタフェース

 早速サンプルで見ていくことにしましょう。以下のAnimalInterface.phpファイルを作成してください。

<?php
interface AnimalInterface  // (1)
{
    public function getName(): string;  // (2)
    public function call(): string;  // (3)
    public function eat(): void;  // (4)
}
リスト5 phplesson/chap18/AnimalInterface.php

 リスト5では、(2)のgetName()、(3)のcall()、(4)のeat()の3個のメソッドが記述されていますが、その全てが抽象メソッドです。このように抽象メソッドのみで構成されたものをインタフェースといいます。インタフェースはもはやクラスとは呼びません。従って、定義するときも(1)のようにclass宣言ではなく「interface」で宣言します。逆に、interfaceで宣言した場合は、その中には抽象メソッドしか記述できません。そのため、それぞれのメソッドにはもはや「abstract」を付ける必要がありません。

注意!!「インタフェース中の定数」

 インタフェースに記述できるものは抽象メソッドのみ、と上で書きましたが、実はもう1つ記述できるものがあります。それは定数です。例えば以下のようなコードです。

interface ConstAndMethod
{
    const NUM = 100;
    public function calcWithConst(): void;
}

インタフェースは継承ではなく実装

 さて、上で定義したインタフェースを利用したクラスを作成する場合、それは継承とは言わず「実装」と言います。キーワードもextendsではなく「implements」となります。実際にサンプルで見て見ましょう。以下のPigクラスを作成してください。

<?php
class Pig implements AnimalInterface  // (1)
{
    public function getName(): string  // (2)
    {
        return "とんちゃん";
    }
 
    public function call(): string  // (3)
    {
        return "ぶうぶう";
    }
 
    public function eat(): void  // (4)
    {
        print("おいしいよ!");
    }
}
リスト6 phplesson/chap18/Pig.php

 リスト6の(1)で下記の記述があります。

implements AnimalInterface

 これによって、PigクラスはAnimalInterfaceを実装したことになります。その際、抽象クラス同様に、インタフェースに記述された抽象メソッドを全て実装する必要があります。Pigクラスでは、(2)〜(4)が該当します。

実装する必要がないメソッドがある場合

 ここで、リスト6の(4)のeat()メソッドに注目します。Pigクラスではこのメソッドを実装して、「おいしいよ!」と表示するようにしています。一方、クラスによっては、このeat()メソッドを実装する必要がない場合というのが出てきます。その場合は、このメソッドを実装しなくてもいいのかというと、それは許されません。例えば、Pigクラスの(4)のメソッドを削除した状態でPigクラスを利用すると、それはエラーとなります。かといって、無意味なソースコードを書くわけにもいきません。

 そういった場合の対処法をサンプルで見てみましょう。以下のTigerクラスを作成してください。

<?php
class Tiger implements AnimalInterface
{
    public function getName(): string
    {
        return "とらお";
    }
 
    public function call(): string
    {
        return "がお〜";
    }
 
    public function eat(): void {}  // (1)
 
}
リスト7 phplesson/chap18/Pig.php

 注目するのは(1)の部分です。(1)ではeat()メソッドに{}(波かっこ)が記述されているので、抽象メソッドではありません。一方、処理は全く記述されていません。このように、「メソッドは実装するが処理を記述しない」という方法があります。

 インタフェースを実装する場合、そのインタフェースに記述されたメソッドは全て実装する必要がありますが、処理が不要な場合はリスト7の(1)ように処理のないように記述すればいいのです。

注意!!「複数のインタフェースを実装」

 前回のコラム「多重継承」にも書きましたが、継承関係では親クラスとして指定できるのは1つだけです。これは、抽象クラスでも同じです。

 一方、インタフェースを実装する場合は、複数のインタフェースを指定できます。その場合は、以下のようにimplementsの後にカンマ区切りでインタフェース名を並べます。

class Something implements Onething, Anotherthing

ポリモーフィズム

 インタフェースが何かを理解してもらえたところで、このインタフェースを実装したPigクラスとTigerクラスを使いながら今回のテーマであるポリモーフィズムを解説しましょう。

見た目は同じでも中身が違うポリモーフィズム

 早速、以下のcallAnimals2.phpを作成してください。

<?php
require_once("AnimalInterface.php");
require_once("Pig.php");
require_once("Tiger.php");
 
function invokeAnimal(AnimalInterface $animal) {  // (1)
    print($animal->getName()."の鳴き声は".$animal->call()."<br>");  // (2)
}
 
$pig = new Pig();  // (3)
$tiger = new Tiger();  // (4)
 
invokeAnimal($pig);  // (5)
invokeAnimal($tiger);  // (6)
リスト8 phplesson/chap18/callAnimals2.php

 このファイルを実行してください。結果は以下のようになります。

とんちゃんの鳴き声はぶうぶう
とらおの鳴き声はがお〜

 リスト8では(3)と(4)でAnimalInterfaceを実装したPigクラスとTigerクラスをnewしています。今までのパターンでは、この後、この各クラスに実装されたメソッドを実行するところなのですが、両クラスとも同じメソッドであるgetName()とcall()を持っています。

 そこで、この両メソッドの呼び出しを関数としてまとめています。それが(1)であり、関数内で実際にメソッドを呼び出して表示させているのが(2)です。そのようにして作成した関数に対して、newしたインスタンスを渡して処理してもらっているのが(5)と(6)です。(5)でPigを、(6)でTigerを渡しています。

 ここで、ソースコードと実行結果をよく見てください。PigとTigerという2種のクラスに対して(5)と(6)で同じ関数を利用して、(2)で同じメソッドを呼び出しています。これが可能なのは、PigもTigerも同じインタフェースであるAnimalInterfaceを実装しているからです。簡単に言うと、PigもTigerも外から見ると形としては同じなのです。なのに、中の実装が違うために実行結果が変わってきます。

 このように、見た目は同じ形でありながら、実行結果が変わってくることを「ポリモーフィズム(多態性)」といい、オブジェクト指向言語の三大特徴の1つなのです。

ポリモーフィズムは実生活にあふれている

 オブジェクト指向の三大特徴は、カプセル化、継承、ポリモーフィズムです。カプセル化は本連載の第14回で扱いました。継承は前回扱いました。ポリモーフィズムが最後に残ったもので、カプセル化や継承に比べてなにやら難しい話に思えてくるかもしれませんが、このポリモーフィズムは実は実世界で見つけることができます。

 1つ例を挙げましょう。それは、車です。最近の車はガソリン車やディーゼル車だけでなく、ハイブリッド車やEV車があります。それらの車は全て動く仕組みが違います。ガソリン車とディーゼル車は似ているかもしれませんが、ガソリン車とEV車では仕組みが全然違います。でも、運転の方法は同じです。アクセルを踏めば前に進み、ハンドルを右に切れば右に曲がります。この「アクセルを踏めば前に進む」というのがインタフェースなのです。

 「踏めば前に進む」という取り決め=シグネチャだけがあり、その実装はそれぞれに任せています。ガソリン車ではピストンが動きますが、EV車ではモーターです。処理内容は全く違います。それでも同じように運転できるのはインタフェースのおかげです。これがポリモーフィズムです。

コラム「抽象クラスかインタフェースか」

 抽象クラスとインタフェース、その使い分けについて補足しておきます。

 抽象クラスは、抽象メソッド以外は普通のクラスなので、値を保持するプロパティや処理を記述した通常のメソッドが含まれます。基本の処理は共通化しておき、差分だけ子クラスに強制実装させたい場合に使用します。

 一方、インタフェースは、メソッドの枠組みだけしか記述できませんので、処理内容は全て実装先クラスに委ねます。

 差分だけ実装させるのか、全てを実装させるのかで抽象クラスかインタフェースかを使い分けます。


無名クラス

 最後に無名クラスを簡単に紹介しておきましょう。本連載第11回で無名関数を紹介しましたが、それのクラス版です。サンプルで見てみましょう。以下のcallAnimals3.phpを作成し、実行してください。

<?php
require_once("AnimalInterface.php");
 
function invokeAnimal(AnimalInterface $animal) {
    print($animal->getName()."の鳴き声は".$animal->call()."<br>");
}
 
invokeAnimal(
    new class implements AnimalInterface  // (1)
    {
        public function getName(): string
        {
            return "名なしさん";
        }
    
        public function call(): string
        {
            return "どんな鳴き声なんだろう";
        }
    
        public function eat(): void
        {
            print("取りあえず食べてみよう!");
        }
    }
);
リスト9 phplesson/chap18/callAnimals3.php

 ポイントはリスト9の(1)です。invokeAnimal()関数はリスト8と同じものです。リスト8ではこの引数としてPigなどのクラスをnewしたものを渡していました。リスト9ではこの引数中で下記のように、いきなりクラス定義を記述しています。

new class …

 この(1)のクラスにはクラス名がありません。

 このように、実行php中でクラス名もなくクラス定義を記述し、そのクラスのインスタンスを生成するものを「無名クラス」といいます。

 まさに、無名関数のクラス版であり、使い捨てのクラスです。このような書き方も可能なことを頭の片隅にでも入れておいてください。

次回はトレイトについて

 次回は、クラスを水平方向に拡張できる「トレイト」を扱います。

今回のサンプルコード

 今回のサンプルコードはこちらからダウンロードできます。

前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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