TemplateMethodパターンをPHPで書いてみた


TemplateMethodパターンとは?

このパターンの役割は、重複した実装の洗練・整理です。
流れは一緒だけれども、内部の処理が違う処理においてそれぞれの処理を分けることが出来ます。

なので、使い所としては、複数種類のフォーマットで出力をするとき(.txt, .html, .csvなど)などに使えます。

  • AbstractClass : 処理の枠組みを定義するクラス。このクラスの中に、枠組みを決めるTemplateMethodが存在しています。
  • ConcreteMethod : AbstractClassを継承するサブクラス。Abstractで定義された抽象メソッドの中身を実装しています。

今回は、こちらの本のに書かれているコードを参考に作成しました。
こちらはJavaで書かれていますので、それをPHPに書き直してみました。

TemplateMethodパターン適用前

今回は、例としてコーヒーと紅茶を淹れるプログラムを作ります。

テンプレートメソッドパターンを適用させる前に、一度そのままコードを書いてみましょう。

<?php
class Coffee {
    public function describeBeverage() {
        echo "--------------------------------\n";
        echo 'コーヒーを作ります。\n';
    }

    public function boilWater() {
        echo'お湯を沸かします。\n';
    } 

    public function brew() {
        echo 'コーヒー豆を挽き、ドリップします。\n';
    }

    public function pourInCup() {
        echo 'カップに注ぎます。\n';
    }

    public function addContents() {
        echo '砂糖とミルクを足します。\n';
    }

    public function makeBeverage() {
        $this->describeBeverage();
        $this->boilWater();
        $this->brew();
        $this->pourInCup();
        $this->addContents();
    }
}
<?php
class Tea {
    public function describeBeverage() {
        echo "--------------------------------\n";
        echo "紅茶を作ります。\n";
    }

    public function boilWater() {
        echo "お湯を沸かします。\n";
    } 

    public function brew() {
        echo "ティーバッグを浸します。\n";
    }

    public function pourInCup() {
        echo "カップに注ぎます。\n";
    }

    public function addContents() {
        echo "レモンを足します。\n";
    }

    public function makeBeverage() {
        $this->describeBeverage();
        $this->boilWater();
        $this->brew();
        $this->pourInCup();
        $this->addContents();
    }
}
<?php
require_once 'Coffee.php';
require_once 'Tea.php';

$coffee = new Coffee();
$tea    = new Tea();

$coffee->makeBeverage();
$tea->makeBeverage();
--------------------------------
【コーヒーを作ります】
お湯を沸かします
コーヒー豆を挽き、ドリップします
カップに注ぎます
砂糖とミルクを足します
--------------------------------
【紅茶を作ります】
お湯を沸かします
ティーパックを浸します
カップに注ぎます
レモンを足します

出来ましたか?確かに動いていますね!では、なぜテンプレートメソッドを使う必要があるのでしょうか?

CoffeeクラスとTeaクラス、よく見ると同じ処理をそれぞれで書いていますね。
少なくともboilWater()pourInCup()は全く同じことをやっています。
他に飲み物が増えた時、全てのクラスに同じ処理を書くのはしんどいですよね…そこで、これらの共通する処理を枠組みとして定義するのがテンプレートメソッドパターンです!!!!!

まず、共通する処理を定義する抽象クラスを作成しましょう。
ここでは、各クラスの共通処理をメソッドとして定義し、クラスによって異なる処理を抽象メソッドとして定義します。
これを行うことによって、サブクラスはクラス独自の処理(今回の場合だと、brew()addContents())だけを記述するだけで良くなります。

TemplateMethodパターン適用後

<?php
abstract class CaffeineBeverage {

    /**
     *  各クラスの共通の処理
     */
    public function describeBeverage($beverage_name) {
        echo "--------------------------------\n";
        echo "【" . $beverage_name . "を作ります】\n";
    }

    public function boilWater() {
        echo "お湯を沸かします\n";
    }

    public function pourInCup() {
        echo "カップに注ぎます\n";
    }


    /**
     *  各クラスによって異なる処理。抽象クラスとして定義
     *  中身はサブクラスで実装してもらう
     */
    abstract protected function brew();

    abstract protected function addContents();


    /**
     *  TemplateMethod
     */
    public function makeBeverage($beverage_name) {
        //一連の動作を定義
        $this->describeBeverage($beverage_name);
        $this->boilWater();
        $this->brew();
        $this->pourInCup();  
        $this->addContents();
    }
}

そして、この抽象クラスを継承したサブクラス(CoffeeクラスとTeaクラス)は、それぞれ独自の処理を記述するだけです。

<?php
require_once 'CaffeineBeverage.php';

class Coffee extends CaffeineBeverage {
    //それぞれのクラス独自の処理のみを実装する
    protected function brew() {
        echo "コーヒー豆を挽き、ドリップします\n";
    }

    protected function addContents() {
        echo "砂糖とミルクを足します\n";
    }
}
<?php
require_once 'CaffeineBeverage.php';

class Tea extends CaffeineBeverage {
    //それぞれのクラス独自の処理のみを実装する
    protected function brew() {
        echo "ティーバッグを浸します\n";
    }

    protected function addContents() {
        echo "レモンを足します\n";
    }
}

どうでしょう?テンプレートメソッドを適用させたことによって、CoffeeクラスとTeaクラスがだいぶスッキリして見えませんか?
飲み物が増えたとしても、CaffeineBeverageクラスを継承して独自の処理だけ付け足せば良いので、とても楽ですね。
ただ、作り方が全く違ってたり、独自のステップ(例えば30分蒸らすとか)がある場合は、テンプレートメソッドの管轄外から外れてしまうので出来ません。

参考書籍

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本