0
0

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 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パターンはその処理の知性を、設計の柔軟性として昇華する技法である。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?