GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Composite(コンポジット・パターン)
Compositeパターン(コンポジット・パターン)とは、GoF (Gang of Four; 4人のギャングたち) によって定義された デザインパターンの1つである。「構造に関するパターン」に属する。Compositeパターンを用いるとディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる。
Composite パターンにおいて登場するオブジェクトは、「枝」と「葉」であり、これらは共通のインターフェースを実装している。そのため、枝と葉を同様に扱えるというメリットがある。
UML class and object diagram
UML class diagram
□ 備忘録
書籍「増補改訂版Java言語で学ぶデザインパターン入門」の引用ですが、腹落ちしました。
ディレクトリの中には、ファイルが入っていたり、別のディレクトリ(サブディレクトリ)が入っていたりします。そしてまた、そのサブディレクトリの中には他のファイルやサブディレクトリが入っていることもあります。
ディレクトリは、そのような「入れ子」になった構造、再帰的な構造を作り出しています。
... (snip)
Compositeパターンは、このような構造を作るためのものであり、容器と中身を同一視し、再帰的な構造を作るデザインパターン
■ "Composite"のサンプルプログラム
実際に、Compositeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- ルートエントリの
ディレクトリ
に、サブディレクトリ
およびファイル
を追加してみる - ルートエントリの
ディレクトリ
に、ユーザエントリのディレクトリ
を追加して、さらに、サブディレクトリ
およびファイル
を追加してみる - 敢えて、
ファイル
に、ディレクトリ
を追加して、失敗することを確認する
$ python Main.py
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.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)
Occurring Exception...
FileTreatmentException
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Composite
- ディレクトリ構成
.
├── Main.py
└── entry.py
(1) Leaf(葉)の役
「中身」を表す役です。この役の中には、他のものを入れることができません。
サンプルプログラムでは、File
クラスが、この役を努めます。
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 _printList(self, prefix=''):
print("{0}/{1}".format(prefix, self))
(2) Composite(複合体)の役
「容器」を表す役です。Leaf
役や、Composite
役を入れることができます。
サンプルプログラムでは、Directory
クラスが、この役を努めます。
class Directory(Entry):
def __init__(self, name):
self.name = name
self.directory = []
def getName(self):
return self.name
def getSize(self):
size = 0
for d in self.directory:
size += d.getSize()
return size
def add(self, entry):
entry.path = self.name
self.directory.append(entry)
def _printList(self, prefix=''):
print(prefix + "/" + str(self))
for e in self.directory:
e._printList(prefix + '/' + self.name)
(3) Componentの役
Leaf
役とComposite
役を同一視するための役です。Component
役は、Leaf
役とComposite
役に共通のスーパークラスとして実現します。
サンプルプログラムでは、Entry
クラスが、この役を努めます。
from abc import ABCMeta, abstractmethod
... (snip)
class Entry(metaclass=ABCMeta):
@abstractmethod
def getName(self):
pass
@abstractmethod
def getSize(self):
pass
def add(self, entry):
raise FileTreatmentException
def printList(self):
self._printList()
@abstractmethod
def _printList(self, prefix=''):
pass
def __str__(self):
return "{0} ({1})".format(self.getName(), self.getSize())
(4) Client(依頼人)の役
サンプルプログラムでは、startMain
メソッドが、この役を努めます。
from abc import ABCMeta, abstractmethod
from entry import Directory, File, FileTreatmentException
def startMain():
try:
print("Making root entries...")
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.printList()
print("")
print("Making user entries...")
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.tex", 300))
tomura.add(File("game.doc", 400))
tomura.add(File("junk.mail", 500))
rootdir.printList()
print("")
print("Occurring Exception...")
tmpfile = File("tmp.txt", 100)
bindir = Directory("bin")
tmpfile.add(bindir)
except FileTreatmentException as ex:
print(ex.message)
if __name__ == '__main__':
startMain()
(5) その他
例外クラスを追加します
class FileTreatmentException(Exception):
def __init__(self,*args,**kwargs):
self.message = "FileTreatmentException"