Python デザインパターン サンプルコード Visitor
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」
(2023-11-19)Python3.11で動作確認済み
root = bin = vi(10000) = latex(20000) = tmp = usr = yuki = diary.html(100) = Composite.java(200) = hanako = memo.tex(300) = tomura = game.doc(400) = junk.mail(500)このようなデータ構造に対して,Composite パターンでも Visitor パターンでも対応できることになる。逆に Visitor パターンの対象はこのような構造になっていなければならないということではない。
Element Visitor (accept) (visit, visitFile, visitDirectory) ↑ ↑ Entry ListVisitor (getName, getSize, add) (visitFile, visitDirectory) ↑ ↑ File Directory (getName, getSize, accept) (getName, getSize, add, accept)class Element は,Java ではインターフェイスです。Visitor を受け入れるインターフェイスであり処理される要素です。
usrdir = Directory("usr") # "usr"をインスタンス化してインスタンス「usrdir」を得る rootdir.add(usrdir) # 「usrdir」をインスタンス「rootdir」のリストに加える yuki = Directory("yuki") # "yuki"をインスタンス化してインスタンス「yuki」を得る usrdir.add(yuki) # 「yuki」をインスタンス「usrdir」のリストに加える yuki.add(File("diary.html", 100)) # "diary.html"をインスタンス化して # インスタンス「yuki」のリストに加えるこのようにしてすべてのノード(ファイルとフォルダ(ディレクトリ))はインスタンス化されます。そのうち各ディレクトリの各インスタンスはそれぞれリストを持っています(リストはコンストラクタで作られる)。各ディレクトリの中身(子ノード)はそのリストに入ることになります。上の例はリストが3つあり最後のファイルも含めて4階層になっていることが判ると思います。
it = iter(directory.dir) for entry in it: entry.accept(self) self.currentdir = savedir (メインルーチンからは最初の1回だけ) rootdir.accept(ListVisitor())
def accept(self, v): v.visit(self)
71 72 73 74 75 76 77 78 79 80
def visitFile(self, file): # ファイルを訪問したときに呼ばれる sys.stdout.write("{0}/{1}\n".format(self.currentdir, file)) def visitDirectory(self, directory): # ディレクトリを訪問したときに呼ばれる sys.stdout.write("{0}/{1}\n".format(self.currentdir, directory)) savedir = self.currentdir self.currentdir = "{0}/{1}".format(self.currentdir, directory.getName()) it = iter(directory.dir) for entry in it: entry.accept(self) self.currentdir = savedir
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys from abc import ABCMeta, abstractmethod class Visitor(metaclass=ABCMeta): def visit(self, obj): # Javaのオーバーロードの代わり if isinstance(obj, File): self.visitFile(obj) elif isinstance(obj, Directory): self.visitDirectory(obj) @abstractmethod def visitFile(self, file): pass @abstractmethod def visitDirectory(self, directory): pass class Element(metaclass=ABCMeta): @abstractmethod def accept(self, v): pass class Entry(Element, metaclass=ABCMeta): @abstractmethod def getName(self): # 名前を得る pass @abstractmethod def getSize(self): # サイズを得る pass def add(self, entry): # エントリを追加する raise FileTreatmentException def __str__(self): # インスタンスをprintすると呼ばれます return "{} ({})".format(self.getName(), self.getSize()) class File(Entry): def __init__(self, name, size): self.name = name self.size = size def getName(self): return self.name def getSize(self): return self.size def accept(self, v): v.visit(self) class Directory(Entry): def __init__(self, name): self.name = name # ディレクトリの名前 self.dir = [] # ディレクトリエントリの集合 def getName(self): # 名前を得る return self.name def getSize(self): # サイズを得る self.size = 0 it = iter(self.dir) for entry in it: self.size += entry.getSize() return self.size def add(self, entry): self.dir.append(entry) return self def accept(self, v): # 訪問者の受け入れ v.visit(self) class ListVisitor(Visitor): def __init__(self): self.currentdir = "" # 現在注目しているディレクトリ名 def visitFile(self, file): # ファイルを訪問したときに呼ばれる sys.stdout.write("{}/{}\n".format(self.currentdir, file)) def visitDirectory(self, directory): # ディレクトリを訪問したときに呼ばれる sys.stdout.write("{}/{}\n".format(self.currentdir, directory)) savedir = self.currentdir self.currentdir = "{}/{}".format(self.currentdir, directory.getName()) it = iter(directory.dir) for entry in it: entry.accept(self) self.currentdir = savedir class FileTreatmentException(Exception): pass def main(): sys.stdout.write("Making root entries...\n") rootdir = Directory("root") bindir = Directory("bin") tmpdir = Directory("tmp") usrdir = Directory("usr") rootdir.add(bindir) rootdir.add(tmpdir) rootdir.add(usrdir) bindir.add(File("vi", 10000)) bindir.add(File("latex", 20000)) rootdir.accept(ListVisitor()) sys.stdout.write("Making user entries\n") yuki = Directory("yuki") hanako = Directory("hanako") tomura = Directory("tomura") usrdir.add(yuki) usrdir.add(hanako) usrdir.add(tomura) yuki.add(File("diary.html", 100)) yuki.add(File("Composite.java", 200)) hanako.add(File("memo.txt", 300)) tomura.add(File("game.doc", 400)) tomura.add(File("junk.mail", 500)) rootdir.accept(ListVisitor()) if __name__ == '__main__': main() """ Making root entries... /root (30000) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (0) Making user entries /root (31500) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (1500) /root/usr/yuki (300) /root/usr/yuki/diary.html (100) /root/usr/yuki/Composite.java (200) /root/usr/hanako (300) /root/usr/hanako/memo.txt (300) /root/usr/tomura (900) /root/usr/tomura/game.doc (400) /root/usr/tomura/junk.mail (500) """