KnowHow

技術的なメモを中心にまとめます。
検索にて調べることができます。

Python基礎 オブジェクト指向21(Visitor) 振る舞いに関するデザインパターン

登録日 :2026/01/31 19:20
カテゴリ :Python基礎

(オブジェクトの種類)

Visitorとは
データ構造を表すクラスとそれに対する操作を行うクラスを分離するパターン

  • 「操作を行うクラス」が「データ構造を表すクラス」を訪問し、順に処理を行う
  • 「データ構造を表すクラス」には操作を記述する代わりに、訪問者を受け入れるためのメソッドを用意する

構成要素

「操作を行うクラス」

Visitor

  • 訪問者となる抽象クラスまたはインターフェース
  • 引数に渡されたデータ構造を操作するためのAPIを定義
  • 操作するデータ構造の型ごとにAPIを定義することもある

ConcreteVisitor

  • Visitorを継承(実装)するクラス
  • データ構造を操作する具体的な処理を実装する

「データ構造を表す」

Element

  • データ構造を表すための抽象クラスまたはインターフェース
  • Visitorのオブジェクトを受け入れルための具体的な処理を実装する

ConcreteElement

  • Elementを継承(実装)するクラス
  • Visitorのオブジェクトを受け入れるための具体的な処理を実装する

オブジェクト指向的要素

「ダブルディスパッチ」を利用

  • もう門者と受け入れる側がお互いのメソッドを呼び出す
    受け入れる側のメソッド:element.accept(visitor)
    訪問する側のメソッド:visitor.visit(element)

  • Elementの具体的な型とVisitorの具体的な型によって実行される処理が確定する

例)
ダブルディスパッチのイメージ:訪問販売

  • 受け入れる側:各家庭
  • 訪問する側:販売員(各家庭のデータを参照して、保険や薬、新聞などを販売する)

メリット・デメリット

メリット

  • データ構造と操作を分離し、共通な操作を一箇所にまとめることができる
  • 新しい操作(Visitor)を容易に追加できる

デメリット

  • 新しいデータ構造を追加することは困難

使い所

  • ツリー構造のような複雑なオブジェクト構造のすべての要素に対して、ある操作を実行する必要がある場合
    例)ディレクトリツリー、組織階層、DOMツリー

  • 特定のデータ構造に対して、操作を色々変化させたい場合
    データ構造を固定して操作を変化させる

サンプル

組織階層に対して操作を行う

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List


class Entry(ABC):
    def __init__(self, _code: str, _name: str):
        self.__code = _code
        self.__name = _name

    @property
    def code(self) -> str:
        return self.__code

    @property
    def name(self) -> str:
        return self.__name

    @abstractmethod
    def get_children(self) -> List[Entry]:
        pass

    @abstractmethod
    def accept(self, visitor: Visitor):
        pass


class Group(Entry):
    def __init__(self, _code: str, _name:str):
        super().__init__(_code, _name)
        self.__entries: List[Entry] = []

    def add(self, entry: Entry):
        self.__entries.append(entry)

    def get_children(self) -> List[Entry]:
        return self.__entries

    def accept(self, visitor: Visitor):
        visitor.visit(self)


class Employee(Entry):
    def __init__(self, _code: str, _name:str):
        super().__init__(_code, _name)

    def get_children(self) -> List[Entry]:
        return []

    def accept(self, visitor: Visitor):
        visitor.visit(self)


class Visitor(ABC):
    @abstractmethod
    def visit(self, entry: Entry):
        pass


class ListVisitor(Visitor):
    def visit(self, entry: Entry):
        if type(entry) == Group:
            print(f"{entry.code}: {entry.name}")
        else:
            print(f"  {entry.code}: {entry.name}")
        for child in entry.get_children():
            child.accept(self)


class CountVisitor(Visitor):
    def __init__(self):
        self.__group_count = 0
        self.__employee_count = 0

    @property
    def group_count(self) -> int:
        return self.__group_count

    @property
    def employee_count(self) -> int:
        return self.__employee_count

    def visit(self, entry: Entry):
        if type(entry) == Group:
            self.__group_count += 1
        else:
            self.__employee_count += 1
        for child in entry.get_children():
            child.accept(self)


if __name__ == "__main__":
    root_entry = Group("01", "Head Office")
    root_entry.add(Employee("001", "CE0"))
    root_entry.add(Employee("002", "CF0"))

    group1 = Group("10", "Japan")
    group1.add(Employee("1001", "A1"))

    group2 = Group("11", "Canada")
    group2.add(Employee("1101", "C1"))
    group2.add(Employee("1102", "C2"))
    group2.add(Employee("1103", "C3"))

    group1.add(group2)
    root_entry.add(group1)

    list_visitor = ListVisitor()
    count_visitor = CountVisitor()

    print(f"Group: {count_visitor.group_count}")
    print(f"Employee: {count_visitor.employee_count}")

    root_entry.accept(list_visitor)
    root_entry.accept(count_visitor)

    print('-'*10)

    print(f"Group: {count_visitor.group_count}")
    print(f"Employee: {count_visitor.employee_count}")