概要
訪問者パターン(Visitor Pattern)は、データ構造とその上での操作を分離する行動型デザインパターンです。このパターンでは、要素の集合に対して新たな操作を追加する際に、既存のデータ構造を変更することなく実現できます。
パターンの核心は、各要素が accept() メソッドを持ち、これにより外部の「訪問者」を受け入れる仕組みにあります。訪問者は要素ごとに異なる処理を定義でき、同じ要素群に対しても多様な振る舞いを実現します。内部では、要素が訪問者の visit() メソッドを呼び出し、自身を引数として渡すことで、適切な処理が動的に選択されます。
このプロセスは「擬似動的二重ディスパッチ」と呼ばれ、Java のような静的単一ディスパッチ言語でも、2段階のメソッド呼び出しによって、要素と訪問者の実際の型に基づいた処理を実現します。
適用可能なケース
- データ構造が安定しており、それに適用される操作が頻繁に変化する場合
- データ構造とその操作を明確に分離したい場合
- 型ごとの処理を if-else や instanceof を使わずに実装したい場合
構成要素
- Visitor(抽象訪問者):各 ConcreteElement に対応する
visit()メソッドを宣言 - ConcreteVisitor:具体的な操作を実装
- Element(抽象要素):
accept(Visitor)メソッドを定義 - ConcreteElement:
accept()の実装で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 Framework:
BeanDefinitionVisitorは Bean 定義の各プロパティを巡回して値解決を行う
メリットとデメリット
メリット
- 操作の追加が容易で拡張性が高い
- データ構造と操作の関心が分離され、単一責任原則に準拠
- 型ごとの処理を明示的に記述でき、条件分岐を排除
デメリット
- 新しい要素タイプを追加すると、すべての Visitor に修正が必要(開放閉鎖原則に反する)
- 要素の構造変更が訪問者全体に影響を及ぼす
- Visitor が具体的な要素クラスに依存するため、依存関係逆転の原則に違反
補足:動的ディスパッチとの関係
Java は静的多重ディスパッチ(メソッドオーバーロード)と動的単一ディスパッチ(メソッドオーバーライド)をサポートします。訪問者パターンでは、accept() 呼び出しで要素の実際の型に基づくディスパッチ(1回目)、その後 visit(this) で訪問者の実際の型に基づくディスパッチ(2回目)を行うことで、擬似的に「二重ディスパッチ」を実現しています。