Visitor パターン

デザインパターンちゃんと勉強をしようと思ったので結城さんの本を 1 つ 1 つ実装してみる。 前回の Decoratorからの続き。

基本的には Visitor パターンは一群のクラスに対して新たな仮想関数をクラス自体を変更せずに追加できるようにする。そのために、全ての仮想関数を適切に特化させた Visitor クラスを作成する。Visitor はインスタンスへの参照を入力として受け取り、ダブルディスパッチを用いて目的を達する。

クラス図

Visitor PlantUML

自分としての理解・疑問

  • Composite パターンの際にハマった Directory クラスが定義済みになっており使えない問題は、前回は名前を変えて対応したが、今回は名前空間を使ってきちんと対応した。
  • 関数を分けて下に処理や情報を流して行くのとは逆に、継承関係から「上に」処理を流して行く事で Visitor ができ上がる感じ。
  • File や Directory クラスの独立性を上げるために行うパターン

↓ 下記の Element に実装されているインターフェイス内の accept が呼び出される部分がすごく分かり辛い。書籍にも書かれてるがここから visit 戻ってくるイメージがここからは想像しにくいんだと思う。

<?php
$it = $directory->iterator();
$it->rewind(); // カーソルを初期化

while ($it->hasNext()) {
    $entry = $it->next();
    $entry->accept($this); // ここが全然わからん
}
$this->currentdir = $savedir;

PHP でのオーバーロード

PHP はオーバーロードがサポートされていないようなので、どうしようか悩んだ。

ただ __call というマジックメソッドを使ってオーバーロードのような機能は実現できるので、今回はそれで。

<?php
public function __call(string $func, $arg)
{
    // メソッド名がVisit以外は例外を投げる
    if ($func != 'visit') {
        throw new Exception('visit以外のメソッドが呼ばれました');
    }

    $classname = get_class($arg[0]); // アクセスしてきたクラス名の取得

    // クラス名から処理を分ける
    if ($classname === 'Visitor\File') {
        return $this->visitFile($arg[0]);
    } elseif ($classname === 'Visitor\Directory') {
        return $this->visitDirectory($arg[0]);
    }
}

なんかもっとスマートな実装方法ありそうな・・・どうなんだろう、自分の力不足感ある。

良く良く考えるとオーバーロードができない事で ListVisitor クラスを規定する Visitor クラスの存在意義がなくなってしまっている。というか本当に使っていない。機能的な意味では ListVisitor クラスに集約されているとはいえ ListVisitor 以外を増やす場合にこれではあまり意味がない気はする。

マジックメソッド __toString

これも知らなかったんだけど、マジックメソッドで __toString てのがあるらしい。 java のと toString と同様の機能。