0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで実装するVisitorパターン:構造を壊さずに処理を追加する外部アルゴリズム

Posted at

概要

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パターンはその構造化された注入の思想を、オブジェクト指向の本質として表現する技法である。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?