訪問者パターンによるデータ構造と操作の分離

概要

訪問者パターン(Visitor Pattern)は、データ構造とその上での操作を分離する行動型デザインパターンです。このパターンでは、要素の集合に対して新たな操作を追加する際に、既存のデータ構造を変更することなく実現できます。

パターンの核心は、各要素が accept() メソッドを持ち、これにより外部の「訪問者」を受け入れる仕組みにあります。訪問者は要素ごとに異なる処理を定義でき、同じ要素群に対しても多様な振る舞いを実現します。内部では、要素が訪問者の visit() メソッドを呼び出し、自身を引数として渡すことで、適切な処理が動的に選択されます。

このプロセスは「擬似動的二重ディスパッチ」と呼ばれ、Java のような静的単一ディスパッチ言語でも、2段階のメソッド呼び出しによって、要素と訪問者の実際の型に基づいた処理を実現します。

適用可能なケース

  • データ構造が安定しており、それに適用される操作が頻繁に変化する場合
  • データ構造とその操作を明確に分離したい場合
  • 型ごとの処理を if-else や instanceof を使わずに実装したい場合

構成要素

  • Visitor(抽象訪問者):各 ConcreteElement に対応する visit() メソッドを宣言
  • ConcreteVisitor:具体的な操作を実装
  • Element(抽象要素)accept(Visitor) メソッドを定義
  • ConcreteElementaccept() の実装で visitor.visit(this) を呼び出し
  • ObjectStructure:要素の集合を管理し、訪問者を全要素に適用

コード例:従業員評価システム

// 抽象要素
interface Employee {
    void accept(Visitor visitor);
}

// 具体要素
class Engineer implements Employee {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class Manager implements Employee {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

// 抽象訪問者
interface Visitor {
    void visit(Engineer engineer);
    void visit(Manager manager);
}

// 具体訪問者:KPI計算
class KpiVisitor implements Visitor {
    public void visit(Engineer e) {
        System.out.println("エンジニアのKPIを計算");
    }
    public void visit(Manager m) {
        System.out.println("マネージャーのKPIを計算");
    }
}

// 構造オブジェクト
class Team {
    private List<Employee> members = new ArrayList<>();
    
    public void add(Employee e) { members.add(e); }
    
    public void apply(Visitor v) {
        for (Employee e : members) {
            e.accept(v);
        }
    }
}

実世界での応用例

  • ファイルシステムの走査:Java NIO の FileVisitor は、ディレクトリやファイルを訪問しながら処理を行う
  • Spring FrameworkBeanDefinitionVisitor は Bean 定義の各プロパティを巡回して値解決を行う

メリットとデメリット

メリット

  • 操作の追加が容易で拡張性が高い
  • データ構造と操作の関心が分離され、単一責任原則に準拠
  • 型ごとの処理を明示的に記述でき、条件分岐を排除

デメリット

  • 新しい要素タイプを追加すると、すべての Visitor に修正が必要(開放閉鎖原則に反する)
  • 要素の構造変更が訪問者全体に影響を及ぼす
  • Visitor が具体的な要素クラスに依存するため、依存関係逆転の原則に違反

補足:動的ディスパッチとの関係

Java は静的多重ディスパッチ(メソッドオーバーロード)と動的単一ディスパッチ(メソッドオーバーライド)をサポートします。訪問者パターンでは、accept() 呼び出しで要素の実際の型に基づくディスパッチ(1回目)、その後 visit(this) で訪問者の実際の型に基づくディスパッチ(2回目)を行うことで、擬似的に「二重ディスパッチ」を実現しています。

タグ: デザインパターン 訪問者パターン Java

7月22日 16:37 投稿