pythonによるデザインパターン[Vistor]

# Intro

この文章は結城 浩さんの「Java言語で学ぶデザインパターン入門」を、pythonで実装してみたサンプルコードです。

筆者の環境は以下の通りです。Python 3.6.3

まだ修行中の身なので間違いがあると思いますがご了承ください。 今回は「Vister」です。

# Summarise

本書のp154によると、 Vistorとは

データ構造と処理を分離

とあります。データの構造とデータの処理を分離して記述することで部品としての独立性を高め、管理しやすいプログラムになります。

# Practice

## Specification

やることはcompositeパターンと同様です。compositeパターンをまず確認してください。

前と同様に、ファイルとフォルダを模式的に表現したプログラムを作成します。ファイルを表すクラスclass File とフォルダを表すクラスclass Folder を用意し、さらにそれらの抽象クラスclass Entry も考えましょう。

フォルダにファイルをどんどん追加していき( def add )、以下の構造をもつファイルを構成していきます。

root
|-- bin
| |-- vim
| |-- latex
|
|-- tmp
|-- usr
|-- yuki
| |-- diary.html
| |-- composite.py
|
|-- hanako
| |-- memo.tex
|
|-- tomura
|-- game.doc
|-- junk.mail

唯一違う点は、フォルダを表すクラスclass Folder のメソッド def print_line で表示するのではなく、別の class ListVistorというクラスの def visitで、以下のように表示しましょう(ファイル名の後の数字はファイルサイズです)。

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.py (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

## Explanation

前と同様に、ファイルを表すクラスclass File ・フォルダを表すクラスclass Folder 、またその抽象クラスclass Entry を作成しましょう。

前と大きく違うのは一番上にある、抽象メソッド def accept が実装されている、 class Element が作られている点です。class Entryclass Element を継承しているので、class Fileclass Folderdef accept を実装する必要があります。

class Fileclass Folderdef accept は引数v をとって、それのv に対して def visit を実行しています。後から詳しく説明しますが、def visit の引数に自分自身のインスタンス(class File ならclass File 自身)を指定していることに注目しておいてください。

まだ、 def visit の説明をしていませんが、引数vclass ListVistor のインスタンスです。つまり、class Fileclass Folderdef accept を介してのみ、データの処理を行っているのです(データの構造とデータの処理の分離)。

次に今回一番重要なclass ListVistor の説明です。class ListVistor はメソッド def visit しか用意されていないので、その説明だけをします。

まず、引数が filefolder の2つあることに注目してください。先ほど説明したように、 def visit は、Class Fileclass Folderdef accept から呼ばれ、自分自身を引数にとります。つまりClass File から呼ばれる時は file が指定され、class Folder から呼ばれる時は folder が指定されます。 file もしくは folder のどちらが存在しているかによって、すなわちClass Fileclass Folder どちらから def visit が呼ばれたかで条件を分岐しています。

Class File から呼ばれた時は

(フォルダ名)/(ファイル名) (ファイルサイズ)

のように表示しているだけです。

class Folder から呼ばれた時は、 self.currentfolder

self.currentfolder = self.currentfolder + '/' + folder.name

のように書き換えます。その後フォルダの中のファイルが格納してある folder.folder から、一つづつファイルを取り出して、 def accept を実行します。 def acceptClass File から def visit を再帰的に呼び出します。
難しいことをしているようですが、要はフォルダの階層を一つ深くして、先ほど上で見た、Class File から呼ばれた時の処理の

(フォルダ名)/(ファイル名) (ファイルサイズ)

を実行しているだけです。フォルダの階層を一つ深くしているので、結果的には

(フォルダ名)/(フォルダ名)/(ファイル名) (ファイルサイズ)

のように表示します。

まあ、ここのアルゴリズムはVisitorパターンの本質ではないのでわからなくても大丈夫です。ただし重要なことは、データ構造Class Fileclass Folder から分離されたclass ListVistordef visit にデータの処理が行われていることです。ここはしっかりと確認してください。

最後にmain関数を見ましょう。

main関数では最初にフォルダを作成し、その後フォルダ・ファイルを追加していきます。重要なのは処理を実行する以下の部分です。

rootfolder.accept(ListVistor())

class Folderdef acceptclass ListVistor を引数として呼びます。class Folder の内部の def accept では、class ListVistordef vistor を呼び、処理が実行されます。
class Folderclass ListVistor を介してデータの処理を実行していることを確認してください。

## Sample Code

class Visitor という class ListVistor の抽象クラスがありますが、これはただのclass ListVistor 抽象クラスで、他クラスから扱いやすくしているだけなので説明は省きます。

python3 visitor.pyMaking 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.py (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

# Conclusion

少し処理が複雑ですが、重要なのはclass Folderclass ListVistor を介してデータの処理を実行していることと、データ構造Class Fileclass Folder から分離されたclass ListVistordef visit にデータの処理が行われていることです。

これによってデータの構造とデータの処理が分離でき、部品としての独立性が高められ、メンテナンスしやすいプログラムとなります。

個人的には class ListVistorの処理が複雑になり過ぎる恐れがあり、プログラムをパッと見たときにプログラムの内容がわかりにくくなる気がします…どうでしょうか?

# ref

--

--