週末!プログラミング部

ソフトウェア開発ネタを中心に自分でいろいろ調べた内容を自分の勝手な解釈で思うがままに書いくためのブログ。サンプルソースコード、API、プラットフォーム、プログラミング言語、開発環境などを調査、分析して追求いく予定です。

Pythonでデザインパターン 拾参ノ型「Visitor」

今回はPythonの勉強しながらデザインパターンのVisitorをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。

Visitorとは

直訳すると「訪問者」ですね。
wikiには「 アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。」とあります。

wikiの説明から解釈するに、

  • 既存のオブジェクトからアルゴリズムを分離。
    これで既存のオブジェクトはデータ構造だけを持ったオブジェクトになる

  • 分離したアルゴリズムは別のオブジェクトに。
    データ構造の変更をさせずにこのオブジェクトに新しいアルゴリズムを追加していける

・・・みたいな感じでしょうか?難しいですね(;^ω^)

Visitorについて考えてみる

例えば"会社"に"Aさん"が出勤する構図を考えてみます。
(いつものごとくわかりにくい例ですがお許しください。。)

そこで以下のように"会社"クラスと"Aさん"クラスを用意してみました。
"会社"クラスにはAさんを受け入れるaccept()メソッドがあります。
"Aさん"クラスには会社を訪問するvisit()メソッドがあります。

class 会社:
    def __init__(self):
        self.__name = "会社名"

    def getName(self):
        return self.__name

    def accept(self, Aさん):
        # Aさんを受け入れる
        print(Aさん.gatName(), "が", self.__name, "に出勤しました")

class Aさん:
    def __init__(self):
        self.__name = "Aさん名"

    def getName(self):
        return self.__name

    def visit(self, 会社):
        # 会社を訪れる
        print(self.__name, "が", 会社.getName(), "に出勤しました")

# 会社がAさんを受け入れる場合
会社A().accept(Aさん())

# Aさんが会社を訪問する場合
Aさん().visit(会社A())

# いずれも結果は同じ

見ていただければわかると思いますがaccept()もvisit()も意味合いは違えどやりたいことは同じです。
なのでなんとかaccept()とvisit()の処理をなんとか一つにまとめたいですよね
これを実現するのがVisitorパターンかな?

やってみます。
まず訪問者を指すVisitorクラスを用意します。

class Visitor(metaclass=ABCMeta):
    @abstractmethod
    def visit(self, company):
        pass

次に訪問先を指すAcceptorクラスを用意します。

class Acceptor(metaclass=ABCMeta):
    @abstractmethod
    def accept(self, visitor):
        pass

Visitorクラスを使って訪問者を指すVisitorAクラスを実装します。
visit()メソッドはAcceptorクラスの引数を持ち、訪問先の名前を取得しています。

class VisitorA(Visitor):
    def __init__(self):
        self.__name = "Aさん"

    def visit(self, company: Acceptor) -> None:
        print(self.__name, "が", company.getName(), "を訪れました。")

Acceptorクラスを使って訪問先を指すCompanyクラスを実装します。
accept()メソッドはVisitorクラスの引数を持ち、Visitorクラスのvisit()を呼び出しています。

class Company(Acceptor):
    def __init__(self):
        self.__name = "Company"

    def getName(self) -> str:
        return self.__name

    def accept(self, visitor: Visitor) -> None:
        visitor.visit(self)

これのクラスを使用すると以下のようになります。
accept()メソッドを使用しても、visit()メソッドを使用しても結果は同じです。
しかし実装は1カ所にまとめられるようになりました!

> Company().accept(VisitorA())
Aさん が Company を訪れました。
> VisitorA().visit(Company())
Aさん が Company を訪れました。

この例ですとaccept()にあった実装をvisit()に追い出しています。
これによりCompanyクラスは会社名というデータ構造だけになっていますね。

サンプルコード

以下、サンプルコードです。
今度は訪問者としてAさんとBさん、訪問先として会社とデパートを追加してみました。

# coding: utf-8

from abc import ABCMeta, abstractmethod

# 訪問者クラス
class Visitor(metaclass=ABCMeta):
    @abstractmethod
    def visit(self, company):
        pass

    @abstractmethod
    def visit(self, depart):
        pass

# 訪問先クラス
class Acceptor(metaclass=ABCMeta):
    @abstractmethod
    def accept(self, visitor):
        pass

# 会社クラス
class Company(Acceptor):
    def __init__(self):
        self.__name = "Company"

    def getName(self) -> str:
        return self.__name

    def accept(self, visitor) -> None:
        visitor.visit(self)

# デパートクラス
class Depart(Acceptor):
    def __init__(self):
        self.__name = "Depart"

    def getName(self) -> str:
        return self.__name

    def accept(self, visitor) -> None:
        visitor.visit(self)

# 訪問者Aクラス
class VisitorA(Visitor):
    def __init__(self):
        self.__name = "Aさん"

    def visit(self, company: Company) -> None:
        print(self.__name, "が", company.getName(), "を訪れました。")

    def visit(self, depart: Depart) -> None:
        print(self.__name, "が", depart.getName(), "を訪れました。")

# 訪問者Bクラス
class VisitorB(Visitor):
    def __init__(self):
        self.__name = "Bさん"

    def visit(self, company: Company) -> None:
        print(self.__name, "が", company.getName(), "を訪れました。")

    def visit(self, depart: Depart) -> None:
        print(self.__name, "が", depart.getName(), "を訪れました。")

if __name__ == "__main__":
    Company().accept(VisitorA())
    Company().accept(VisitorB())
    Depart().accept(VisitorA())
    Depart().accept(VisitorB())
    print("")
    VisitorA().visit(Company())
    VisitorA().visit(Depart())
    VisitorB().visit(Company())
    VisitorB().visit(Depart())

CompanyやDepartにAさんやBさんが来たとしても、AさんやBさんがCompanyやDepartに行ったとしても結果は同じですね。

$ python Visitor.py
Aさん が Company を訪れました。
Bさん が Company を訪れました。
Aさん が Depart を訪れました。
Bさん が Depart を訪れました。

Aさん が Company を訪れました。
Aさん が Depart を訪れました。
Bさん が Company を訪れました。
Bさん が Depart を訪れました。