一般的には「容器と中身を同一視する」パターンと言われています。
言い換えると、「容器と中身に同一の機能をもたせる」パターンです。
一般的なフォルダ構造のようなItemを作ります。
CompositeItem
(フォルダ)とSimpleItem
(ファイル)するものを作ります。
import abc
import sys
# Component
class AbstractItem(metaclass=abc.ABCMeta):
@abc.abstractproperty
def composite(self):
pass
def __iter__(self):
return iter([])
# Leaf
class SimpleItem(AbstractItem):
def __init__(self, name, price=0.00):
self.name = name
self.price = price
def print(self, indent="", file=sys.stdout):
print("{}${:.2f} {}".format(indent, self.price, self.name), file=file)
@property
def composite(self):
return False
# Composite
class AbstractCompositeItem(AbstractItem):
def __init__(self, *items):
self.children = []
if items:
self.add(*items)
def add(self, first, *items):
self.children.append(first)
if items:
self.children.extend(items)
def remove(self, item):
self.children.remove(item)
def __iter__(self):
return iter(self.children)
# (Composite)
class CompositeItem(AbstractCompositeItem):
def __init__(self, name, *items):
super().__init__(*items)
self.name = name
def print(self, indent="", file=sys.stdout):
print("{}${:.2f} {}".format(indent, self.price, self.name))
for child in self:
child.print(indent + " ")
@property
def composite(self):
return True
@property
def price(self):
return sum(item.price for item in self)
def main():
ruler = SimpleItem("Ruler", 1.60)
eraser = SimpleItem("Eraser", 0.20)
pencil = SimpleItem("Pencil", 0.40)
pencilset = CompositeItem("Pencil Set", pencil, eraser, ruler)
box = SimpleItem("Box", 1.00)
boxedpencilset = CompositeItem("Boxed Pencil Set", box, pencilset)
boxedpencilset.add(pencil)
for item in [pencil, ruler, eraser, pencilset, boxedpencilset]:
item.print()
if __name__ == "__main__":
main()
クラス図は以下のようになっています。
覚える点は3点だけ
この3点を覚えておけばCompositeは実装できると思います。
Compositeパターンが活躍するのは、中身の種類が増えたときです。
例えば、個数つきのItem(NumberingItems
)などの新機能を追加する場合に、AbstractItemを継承し、
インターフェースを実装するだけでOKです。
実践Python3では、すべてをItem
クラスにまとめる方法が紹介されています。
Itemの種類が2種類くらいだったらこちらの方がわかりやすいと思います。
import sys
import itertools
def main():
ruler = Item.create("Ruler", 1.60)
pencil = Item.create("Pencil", 0.40)
eraser = make_item("Eraser", 0.20)
pencilset = Item.compose("Pencil Set", pencil, eraser, ruler)
box = Item.create("Box", 1.00)
boxedpencilset = make_composite("Boxed Pencil Set", box, pencilset)
boxedpencilset.add(pencil)
for item in [pencil, ruler, eraser, pencilset, boxedpencilset]:
item.print()
class Item:
def __init__(self, name, *items, price=0.00):
self.name = name
self.price = price
self.children = []
if items:
self.add(*items)
@classmethod
def create(Class, name, price):
return Item(name, price=price)
@classmethod
def compose(Class, name, *items):
return Item(name, *items)
@property
def composite(self):
return bool(self.children)
@property
def price(self):
return (sum(item.price for item in self) if self.children else self.__price)
@price.setter
def price(self, price):
self.__price = price
def add(self, first, *items):
self.children.extend(itertools.chain((first, ), items))
def remove(self, item):
self.children.remove(item)
def __iter__(self):
return iter(self.children)
def print(self, indent="", file=sys.stdout):
print("{}${:.2f} {}".format(indent, self.price, self.name), file=file)
for child in self:
child.print(indent + " ")
def make_item(name, price):
return Item(name, price=price)
def make_composite(name, *items):
return Item(name, *items)
if __name__ == "__main__":
main()