抽象クラス

抽象クラスとは

抽象クラス」とは、直接インスタンスを生成できないクラスです。
つまり必ず継承して使用するクラスです。

抽象クラスには「抽象メソッド」が定義できます。
抽象メソッドとは処理内容を持たずに名前だけ定義されたメソッドです。

抽象メソッドは、継承先のクラスで必ずオーバーライドする必要があります。
でないとエラーとなります。
つまり抽象クラスでは継承先のクラスに対して特定のメソッドの実装を強制する事が出来るということです。

<?php

abstract class Product
{
    // 抽象メソッド
    abstract public function setPrice();
}

?>

通常のクラスとは違い

abstract class クラス名

という定義の仕方になっています。
abstractというキーワードを前につけることでそのクラスは抽象クラスとして定義されます。

そしてメソッドにもabstractと付いています。これが抽象メソッドです。
抽象メソッドは処理内容を記述することが出来ません。なので「{ }」が存在せず、

abstract アクセス修飾子 function メソッド名();

という形式の定義になります。
また抽象クラスには、抽象メソッドのみではなく通常のクラスのように普通のメソッドやフィールドの定義も可能です。


先にも述べたとおり、抽象クラスはインスタンスの生成が出来ません。

<?php

$prd = new Product(); // エラーとなる

?>
Fatal error: Cannot instantiate abstract class Product in ・・・

なので必ず継承して使用します。
そして前述の通り、必ず継承先では抽象メソッドを実装する必要があります。

<?php

abstract class Product
{
    // 抽象メソッド
    abstract public function setPrice();
}

class FoodProduct extends Product
{
}

?>
Fatal error: Class Product contains 1 abstract method and must therefore be declared 
abstract or implement the remaining methods (Product::setPrice) ・・・

抽象クラスの実装を行わないと、このようにFatal Errorが発生します。
この例ではProductという抽象クラスにsetPriceという抽象メソッドが定義されているため、このクラスを継承するFoodProductクラスでは、setPriceメソッドを必ず実装する必要があります。

抽象クラスの利用

実際に抽象クラスを使った例を見てみましょう。

<?php

// 処理所要時間測定クラス
abstract class TimeMeasurer
{
    abstract protected function process();
    
    public function exec()
    {
        $startTime = $this->getMicrotime();
        $this->process();
        $endTime = $this->getMicrotime();
        $procSecs = $endTime - $startTime;
        echo sprintf('処理所要時間は%s秒でした。', $procSecs);
    }
    
    private function getMicrotime()
    {
        $splitedMt = explode(' ', microtime());
        return $splitedMt[0] + $splitedMt[1];
    }
}

?>

TimerMeasurerという抽象クラスを作成しました。
そして抽象メソッドとしてprocessメソッドを定義しました。この抽象メソッドはprotectedにしました。

もう一つ、execというpublicメソッドがあり、この中でprocessメソッドを間接的に呼び出しています。このexecでやっていることは、processメソッド実行の前後でミリ秒単位の時間を取得し、その差を求めています。
つまり、processメソッド内の処理にどれだけ時間がかかったかを計測するのです。

仕組みとしてはこの抽象クラスを継承し、processメソッドをオーバーライドして処理を実装するだけです。

そしてこのクラスのインスタンスを生成してexecメソッドを実行すれば、processメソッド内の処理にどれだけの時間を要したかがわかる仕組みです。

実際にやってみましょう。

<?php

class TestClass1 extends TimeMeasurer
{
    protected function process()
    {
        for ($i = 0; $i < 1000000; $i++) {
            $str .= 1;
        }
    }
}

class TestClass2 extends TimeMeasurer
{
    protected function process()
    {
        for ($i = 0; $i < 1000000; $i++) {
            $str .= '1';
        }
    }
}

$test1 = new TestClass1();
$test1->exec();

$test2 = new TestClass2();
$test2->exec();

?>
TestClass1の処理所要時間は0.656418800354秒でした。
TestClass2の処理所要時間は0.298094034195秒でした。

TimerMeasurerを継承するクラスを二つ作りました。
文字列に対しての結合の際に、数値を結合するのと文字列を結合するのと、どちらが時間がかかるかという実験です。
processメソッドを実装し、時間を計りたい処理を記述します。
二つのクラスを見るとほとんど同じに見えますが、
$str .= 1; と $str .= '1';
の部分が違います。

実験結果は見ての通り、文字列に対して文字列を結合するより、文字列に対して数値を結合する方が時間がかかることが分かりました。数値を結合しようとすると、内部で数値を文字列に変換するという処理が入るため、その分遅くなるのです。

実験内容は今回はどうでもいいのですが、
要はある機能を持たせたクラスを抽象クラスとして定義し、これを継承すれば、その機能を知らないうちにも利用している仕組みが出来上がっているというわけです。

実用的な利用

上の例は時間を計るだけの為に少々大げさな感じはしますが、こんな感じの仕組みを利用するとWEBシステムが効率的に組めたりします。
抽象クラスって結構便利なんです。

例えば、サイトへアクセス時に共通で行う処理を抽象クラスとして定義し、各ページごとの処理はこれを継承して抽象メソッドに実装するだけの仕組みを作ることができます。
サイトへアクセス時に共通で行う処理といえば例えばカウンターの加算やアクセスログの記録です。

HttpProcess.php
<?php

abstract class HttpProcessAbstract
{
    abstract protected function main();
    
    public function exec()
    {
        try {
            // カウンター加算処理
            $this->incrementCounter();
            // アクセスログに記録
            $this->logingAccess();
            // ページ特有の処理
            $this->main();
        } catch (Exception $e) {
            $logger = new Logger();
            $logger->write($e->getMessage());
            die('システムエラーが発生しました');
        }
    }
    
    private function logingAccess()
    {
        // なんらかのアクセスログ記録処理を記述
    }
    
    private function incrementCounter()
    {
        // なんらかのカウンター処理を記述
    }
}

?>
IndexProcess.php
<?php

require_once 'HttpProcessAbstract.php';

class IndexProcess extends HttpProcessAbstract
{
    protected function main()
    {
        // なんらかのトップページ特有の処理を記述
    }
}

?>
index.php
<?php
require_once 'IndexProcess.php';
$proc = new IndexProcess();
$proc->exec();
?>

<html>
<head>
<title></title>
</head>
<body>

</body>
</html>

各ページごとのクラスを作成し、かならずHttpProcessAbstractを継承するようにします。そしてすべてのページのHTMLソースの頭に、上記のようなお決まりの3行のスクリプトを入れるだけです。

これでどのページへアクセスしても、カウンターの加算とアクセスログ記録が自動で行われます。そのページ特有の処理が何も無い場合は、とりあえず継承して中身が空のmainメソッドを作っておけばいいだけです。
というより、何もない場合用にNothingProcessとかなんか適当にクラスを作り、中身が空のmainメソッドを作って、何も処理がないページではこれを共通で使えばいい感じです。