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 addfolderclass 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

--

--