概要
Visitor(ビジター)パターンは、
データ構造を変更せずに、さまざまな操作(処理)を要素に対して適用できるようにする設計パターンである。
要素構造(ノードやオブジェクト)と処理ロジック(操作)を分離することで、新しい処理を追加しやすく、構造に手を加えずに振る舞いを拡張できる。
1. なぜVisitorが必要か?
❌ 処理を要素内部に詰め込むと、機能追加のたびに構造をいじる羽目になる
class Node:
def render(self): ...
def export(self): ...
def validate(self): ...
→ 単一責任が崩れ、要素側が過剰に肥大化する
✅ Visitorに処理を分離し、要素はそれを受け入れるだけの構造にする
node.accept(visitor)
→ 処理ロジックは外部化され、構造に手を加えずに拡張が可能
2. 基本構造
✅ Visitorインターフェース
class Visitor:
def visit_file(self, element):
raise NotImplementedError
def visit_folder(self, element):
raise NotImplementedError
✅ Element(訪問される側)
class Element:
def accept(self, visitor: Visitor):
raise NotImplementedError
✅ ConcreteElement(構造を持つオブジェクト)
class File(Element):
def __init__(self, name):
self.name = name
def accept(self, visitor):
visitor.visit_file(self)
class Folder(Element):
def __init__(self, name, children=None):
self.name = name
self.children = children or []
def accept(self, visitor):
visitor.visit_folder(self)
✅ ConcreteVisitor(具体的な操作)
class ExportVisitor(Visitor):
def visit_file(self, element):
print(f"[Exporting file] {element.name}")
def visit_folder(self, element):
print(f"[Exporting folder] {element.name}")
for child in element.children:
child.accept(self)
✅ 使用例
root = Folder("root", [
File("a.txt"),
File("b.txt"),
Folder("sub", [File("c.txt")])
])
visitor = ExportVisitor()
root.accept(visitor)
出力:
[Exporting folder] root
[Exporting file] a.txt
[Exporting file] b.txt
[Exporting folder] sub
[Exporting file] c.txt
3. Python的応用:singledispatchmethod
による簡易Visitor
from functools import singledispatchmethod
class Visitor:
@singledispatchmethod
def visit(self, arg):
raise NotImplementedError
@visit.register
def _(self, arg: File):
print(f"[ファイル処理] {arg.name}")
@visit.register
def _(self, arg: Folder):
print(f"[フォルダ処理] {arg.name}")
for c in arg.children:
self.visit(c)
→ 型ベースで処理を分岐させ、構文的Visitorパターンを構成
4. 実務ユースケース
✅ AST(抽象構文木)に対する変換・解析
→ PythonのAST、SQL構文、Markdownの解析などに応用
✅ ファイルシステムの操作(検証・エクスポート・変換)
→ ディレクトリ構造に対して、目的ごとのVisitorを適用
✅ UIツリーへの処理適用(検証、描画、トラバース)
→ DOM操作やカスタム描画処理の分離
✅ DSL(ドメイン言語)のインタプリタ・トランスパイラ構築
→ 各ノード型に対して意味論的処理を定義
5. よくある誤用と対策
❌ VisitorがElementに過剰に依存して型結合する
→ ✅ Visitorの設計は拡張に留め、Element側を柔らかく保つ
❌ 処理の追加は簡単だが、要素構造の追加が難しくなる
→ ✅ 要素の拡張が必要な場合はBuilderやCompositeと併用
❌ accept() が単なる visitor.visit(self)
の機械的な呼び出しに留まる
→ ✅ 自己参照や再帰構造を活用し、ツリー構造に合わせて設計する
結語
Visitorパターンとは、“構造と処理を分離し、異なるロジックを外部から柔軟に注入する設計”である。
- 構造はそのままに、処理を増やすことが容易
- AST・ファイル・UI・ドキュメントなど、ツリー構造を対象にした汎用的パターン
- Pythonでは
accept()
メソッドと@singledispatchmethod
により、シンプルかつ強力に表現可能
Pythonicとは、“構造に触れず、振る舞いだけを変えること”。
Visitorパターンはその処理の知性を、設計の柔軟性として昇華する技法である。