💬

デザインパターンを学ぶ #19 ビジター(Visitor)

に公開

1. はじめに

今回は ビジターパターン(Visitor)
目的は、オブジェクト構造を変えずに新しい処理を追加できるようにすることです。

複雑な構造体やツリー構造に対して「別の観点の処理」を加えたいときに有効です。

2. Visitorとは?

ビジターパターンでは、処理を「訪問者(Visitor)」として外に切り出します。
対象(Element)は「自分にVisitorを受け入れる(accept)」メソッドを用意し、処理をVisitorに委ねます。

登場役は以下のとおりです:

  • Visitor … 各要素に対する操作を定義するインターフェース
  • ConcreteVisitor … 実際の処理を行うクラス
  • Element … Visitorを受け入れる側(accept()を持つ)
  • ConcreteElement … 実際のデータ構造

これにより、構造はそのままで「新しい処理」をVisitorとして追加できます。

3. 実装イメージ(PHP)

<?php
// Visitorインターフェース
interface Visitor {
    public function visitBook(Book $book): void;
    public function visitMovie(Movie $movie): void;
}

// Elementインターフェース
interface Item {
    public function accept(Visitor $visitor): void;
}

// ConcreteElement A
class Book implements Item {
    public function __construct(public string $title, public int $price) {}
    public function accept(Visitor $visitor): void {
        $visitor->visitBook($this);
    }
}

// ConcreteElement B
class Movie implements Item {
    public function __construct(public string $title, public int $price) {}
    public function accept(Visitor $visitor): void {
        $visitor->visitMovie($this);
    }
}

// ConcreteVisitor
class PricePrinter implements Visitor {
    public function visitBook(Book $book): void {
        echo "Book: {$book->title}, {$book->price} yen\n";
    }
    public function visitMovie(Movie $movie): void {
        echo "Movie: {$movie->title}, {$movie->price} yen\n";
    }
}

// 実行例
$items = [
    new Book("Design Patterns", 3000),
    new Movie("Inception", 2000),
];

$visitor = new PricePrinter();
foreach ($items as $item) {
    $item->accept($visitor);
}

4. メリット・デメリット

メリット

  • オブジェクト構造を変えずに新しい処理を追加できる
  • 処理がVisitorに集約され、見通しが良い

デメリット

  • 新しい要素型(Element)を追加すると、すべてのVisitorに修正が必要
  • 小規模なケースではオーバーエンジニアリングになりがち

5. 使いどころ

  • 構造は安定しているが、処理をどんどん追加したいケース
  • 構文木やAST(抽象構文木)の処理(コンパイラなど)
  • 集計・レポートなど、共通構造に対して複数の集計処理をしたいとき

要素の種類は固定、新しい処理を増やしたい」ならビジターパターンの出番です。

Discussion