感謝のプログラミング 10000時間

たどり着いた結果(さき)は、感謝でした。

PHPでTemplate Methodパターン

<スポンサーリンク>

Template Methodは、複数の手続きの中に共通して存在する手順を、抽象レベルで一般化するためのパターンである。
具体的には、スーパークラスで処理の枠組みを定め、サブクラスでその具体的内容を定めるようなパターンのこと。
GoFの定義は以下の通り。

ある操作におけるアルゴリズムの骨格を定義し、いくつかの処理の定義についてはサブクラスに任せる。そして、アルゴリズムの構造を変更すること無く、その中に含まれる処理の再定義を行う。

もう少し簡単な言葉にすると、Template Methodというのは、色んなクラスで「共通で使う処理」を抽出して、まとめる。
それと、それぞれのクラスで似たようなことを行うんだけど、微妙に中身が違うものは抽象メソッドとしてTemplateに定義する。
抽象メソッドとして定義されることで、必要となる実装が強制されることになる。

クラス図にするとこんな感じ。
f:id:sho322:20131213010423j:plain

サンプル

結城浩先生のJavaデザインパターンの本を参考にして、PHPで書き直してみた。
Java版を見たい方は、結城先生の本を見てほしい。

・AbstractDisplayクラス
これがテンプレートになる。
共通の処理「display」はちゃんと処理内容も書かれている。

<?php

abstract class AbstractDisplay {
    public abstract function open();
    public abstract function write();
    public abstract function close();
    public final function display() {
        $this->open();
        for ($i=0; $i<5;$i++) {
            $this->write();
        }
        $this->close();
    }

}

・StringDisplayクラス
これは、AbstractDisplayを継承している。
文字列を表示するクラス。
抽象メソッドを実装しているが、display()メソッドはAbstractDisplayクラス、すなわち親クラスで定義されたものを使う。これが後々のポイントとなる。

<?php

require_once './AbstractDisplay.php';

class StringDisplay extends AbstractDisplay {
    private $string;
    private $width;
    public function __construct($str) {
        $this->string = $str;
        $this->width = mb_strlen($str);
    }

    public function open() {
        $this->printLine();
    }
    public function write() {
        echo "|" . $this->string . "|", PHP_EOL;
    }
    public function close() {
        $this->printLine();
    }

    private function printLine() {
        echo  "+";
        for($i=0; $i<$this->width; $i++) {
            echo "-";
        }
        echo "+", PHP_EOL;
    }

}

・CharDisplayクラス
文字を表示するクラス。
StringDisplayと同じく、AbstractDisplayを継承している。
display()メソッドは親クラスのもの(AbstractDisplayのメソッド)を使う。

<?php

require_once './AbstractDisplay.php';

class CharDisplay extends AbstractDisplay {
    private $char;
    public function __construct($ch) {
        $this->char = $ch;
    }
    public function open() {
        echo "<<";
    }

    public function write() {
        echo $this->char;
    }

    public function close() {
        echo ">>", PHP_EOL;
    }

}

・client.php
上で作ったクラスたちを使ってみるために作ったもの。

$ cat client.php
<?php

require_once './AbstractDisplay.php';
require_once './CharDisplay.php';
require_once './StringDisplay.php';

$charDisplay = new CharDisplay("V");
$strDisplay = new StringDisplay("hello! string");

$charDisplay->display();
echo "======================", PHP_EOL;
$strDisplay->display();

ここで注目すべきなのは、CharDisplayをインスタンス化したものも、StringDisplayをインスタンス化したものも、両方ともdisplay()メソッドを使っていることだ。
使う側からすると、同じメソッドを叩くだけでよい。つまり、display()を使うってことだけ知っていればいいということ。
そうすれば、それぞれのインスタンスが、適切な処理をやってくれる。


実行した結果は以下の通りになる。
「適切な処理をやってくれる」といった意味がわかるはず。

$ /usr/bin/php client.php
<<VVVVV>>
======================
+-------------+
|hello! string|
|hello! string|
|hello! string|
|hello! string|
|hello! string|
+-------------+

ほら、文字「V」を渡したときは、「VVVVV」と出て、
文字列を渡した時は、うまいこと文字列のための処理をしてくれている。


ちなみに、抽象クラスで定義した抽象メソッドが実装されていないと、以下のようなエラーが出る。
つまり、コンパイラがメソッドの実装漏れを教えてくれる。

Fatal error: Class StringDisplay contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (AbstractDisplay::write) in /home/sho322/php/template_method/StringDisplay.php on line 29

参考にした本

三冊一気に読んでから記事にまとめた。
PHPによるデザインパターン入門
デザインパターンとともに学ぶオブジェクト指向のこころ (Software patterns series)
増補改訂版Java言語で学ぶデザインパターン入門

PHPデザパタ本はやはり、PHPオブジェクト指向を理解するためには必須の本だと思われる。
結城先生の本は説明不要なほどの名著だ。
オブジェクト指向のこころ、という本はけっこう古風な感じなんだけど、言葉を尽くして説明してくれる感じ。コード例はないけど、思想的なことを語ってくれる。