pythonによるデザインパターン[Composite]
# Intro
この文章は結城 浩さんの「Java言語で学ぶデザインパターン入門」を、pythonで実装してみたサンプルコードです。
筆者の環境は以下の通りです。Python 3.6.3
まだ修行中の身なので間違いがあると思いますがご了承ください。 今回は「Composite」です。
# Summrize
本書のp154によると、 Compositeとは
容器と中身を同一視し、再帰的な構造を作るもの
とあります。再帰的な構造と聞くとデータ構造、木構造などを思い出しますが、プログラミングで再帰的な構造はよく登場します(例えば下で説明するファイル — フォルダの関係)。
その構造を上手くデザインしたものがCompositeです。
# Practice
## Specification
ファイルとフォルダを模式的に表現したプログラムを作成しましょう。ファイルを表すクラス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
で以下のように表示しましょう(ファイル名の後の数字はファイルサイズです)。
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 Entry
を作成しましょう。このクラスには
- フォルダの中に、ファイル・フォルダを入れる関数
def add
- ファイル・フォルダの構造を標準出力する関数
def print_line
- ファイルサイズを返す関数
def get_size
- ファイルサイズ付きの文字列を返す関数
def to_string
を実装します。
def add
の中身は単にエラー処理をしているだけなので、無視しても構いません。重要なのはフォルダとファイルを抽象クラスで同じものとして扱う、つまり容器と中身を同一視する点です。
本書にはメソッドのオーバーロードについての説明がありましたが、pythonはメソッドのオーバロードができないため、デフォルト引数として実装します。
次にファイルを表すクラスclass File
を実装しましょう。
attributeとしてはname
,size
をとり、ファイルを生成するときは File('main.py', 100)
という風に書きます。また、def get_size
ではサイズを返します。サイズを返すだけなのでself.size
と書けばいいのですが、抽象クラスに合わせるために(class Folder
の都合上)def get_size
を実装します。
def print_list
ではファイル名/(ファイルサイズ)
という文字列を返します。
最後にフォルダを表すクラスclass Folder
を実装しましょう。
attributeとしてはname
,folder
をとります。このfolder
が重要で、ここにフォルダ・ファイル区別せず、class Entry
を継承しているオブジェクトを入れていきます。そして、 def add
でfolder
にclass Entry
のオブジェクトを追加します。
def get_size
では動的にフォルダのファイルサイズを計算します。このメソッドでは特に下の部分が重要です。
for f in self.folder:
size += f.get_size()
folder
の配列を一つずつ取り出して、def get_size
を実行します。特に重要なのはfolder
の中身はフォルダ・ファイルか意識しなくても良い、ということです。なぜならどちらも def get_size
を持っているので、 class File
なら def get_size
によりattributeの self.size
が返され、class File
なら def get_size
が再帰的に実行され動的なファイルサイズが返されます。単純なようで複雑なことをしているので気をつけて確認してください。
def print_list
も同じようにファイル・フォルダを区別せず、再帰的に文字を表示しています。
## Sample Code
python3 composite.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
一通り確認すると、ただの抽象クラスと再帰関数の組み合わせなだけじゃないか、と思われるかもしれません。恐らくですが、わざわざこのパターンだけを取り出したのは、再帰的な構造が(気づかないくらい)多く登場するからだと思います。今は最初に、これは再帰的な構造をとると明示しましたが、普段プログラムを書いているときにすぐに再帰的な構造と気づけるでしょうか?もし気づけなかったら、変に複雑な設計方法で実装しそうですよね…
だからこそCompositeを頭の片隅に置いておいて、再帰的な構造が出た瞬間に、このような抽象クラスと再帰関数を組み合わせで構成することが思い付きたいですね。
# ref
- 「増補改訂版 Java言語で学ぶデザインパターン入門」 結城浩(著)第11版