概要
Visitor(ビジター)パターンは、
データ構造本体に手を加えることなく、処理ロジック(アルゴリズム)を外部に分離・追加できる設計パターンである。
複雑な構文木やオブジェクト階層に対して、型ごとの処理をオープンにしつつ、構造を閉じたままに保てるのが特徴。
1. なぜVisitorが必要か?
❌ 処理ごとに構造体へメソッドを追加すると、クラスが肥大化
class Node:
def evaluate(self): ...
def print(self): ...
def optimize(self): ...
→ 新しい処理のたびにNodeに手を入れる必要があり、拡張に弱い
✅ 処理を外部クラス(Visitor)として分離し、構造には触れずに拡張可能に
printer = PrintVisitor()
node.accept(printer)
→ 構造は閉じ、操作は開かれている(Open/Closed原則)
2. 基本構造
✅ Elementインターフェース(受け入れ側)
class Node:
def accept(self, visitor):
raise NotImplementedError
✅ Concrete Elements(対象構造)
class Number(Node):
def __init__(self, value):
self.value = value
def accept(self, visitor):
visitor.visit_number(self)
class Add(Node):
def __init__(self, left, right):
self.left = left
self.right = right
def accept(self, visitor):
visitor.visit_add(self)
✅ Visitorインターフェース
class Visitor:
def visit_number(self, number):
raise NotImplementedError
def visit_add(self, add):
raise NotImplementedError
✅ Concrete Visitors(処理を担当)
class PrintVisitor(Visitor):
def visit_number(self, number):
print(number.value, end='')
def visit_add(self, add):
print("(", end='')
add.left.accept(self)
print(" + ", end='')
add.right.accept(self)
print(")", end='')
✅ 使用例
expr = Add(Number(3), Add(Number(1), Number(2)))
printer = PrintVisitor()
expr.accept(printer)
出力:
(3 + (1 + 2))
3. Python的応用:singledispatch
を使って動的ディスパッチ
from functools import singledispatch
@singledispatch
def visit(node):
raise NotImplementedError
@visit.register
def _(node: Number):
print(f"Number({node.value})")
@visit.register
def _(node: Add):
print("Add(")
visit(node.left)
visit(node.right)
print(")")
→ Pythonではマルチディスパッチを使ってビジター風の柔軟な処理が実現可能
4. 実務ユースケース
✅ 構文木の評価・可視化・最適化(AST)
→ Pythonの ast.NodeVisitor
と同様のパターンで解析可能
✅ ファイルシステムやUIの階層構造への処理の注入
→ 各ノードに処理ロジックを後から差し込む
✅ 複数の分析軸にまたがるデータ解析処理(例:CSV → 統計処理 + 可視化)
→ 構造は保ちつつ、分析方法だけ追加できる
✅ ゲーム内のオブジェクト階層に対するイベント伝播
→ Enemy
, Player
, Item
に対してそれぞれ異なる処理をビジターで定義
5. よくある誤用と対策
❌ ビジターが要素に依存しすぎて凝集性が低下
→ ✅ 1つのビジターに1つの処理目的を持たせ、関心を分離する
❌ accept() メソッドの重複が煩雑
→ ✅ 抽象クラスやmixinで共通化・動的ディスパッチの活用を検討
❌ 新しい要素を追加するたびにビジター側の修正が必要になる(逆の拡張に弱い)
→ ✅ 処理が頻繁に追加されるケースに限定して適用することが重要
結語
Visitorパターンとは、“構造に触れずに振る舞いを注入する、外部からのアルゴリズム設計”である。
- 構造の安定性と操作の柔軟性を両立し、開放的な拡張性を確保
- 処理ごとのVisitorを使い分けることで、複雑な処理も構造から独立して設計可能
- Pythonでは明示的ディスパッチと動的型付けを活かし、極めて柔軟に構築可能
Pythonicとは、“変化する処理と不変の構造を分離すること”。
Visitorパターンはその構造化された注入の思想を、オブジェクト指向の本質として表現する技法である。