Compositeパターン
『Rubyによるデザインパターン』(ラス・オルセン著 ピアソン・エデュケーション刊)をPythonで書いています。
ある要素には幾つかの子要素がり、子要素には数個の孫要素がり、
さらに孫要素には・・・であって、
しかも、子も孫もひ孫も、同じようなインターフェイスを備えている、
と、いう構造は良くあります。
例えば、ディレクトリ構造など。
フォルダの中には子フォルダがあり、その子フォルダの中にもまたフォルダがあり・・・。
しかも、どの階層でも『フォルダ』である事には変わりはありません。
このような構造を実現するのが、Compositeパターンです。
ここでは、ケーキを作る工程を考えます。
製品の「ケーキを作る工程」には「生地を作る」「焼く」などの工程が、
「生地を作る」工程には「粉をボウルに入れる」「液体を加える」などの工程があります。
各工程は共通のインターフェイスとして、所要時間を返すget_time_requiredメソッドを備えます。
class Task(object): u"""ケーキ作りの工程""" def __init__(self, name): self.name = name self.parent = None #(オプション)自分の「親」工程 def get_time_required(self): return 0.0 class CompositeTask(Task): u"""いくつかの子工程からなる工程""" def __init__(self, name): Task.__init__(self, name) self.sub_tasks = [] def get_time_required(self): return sum(t.get_time_required() for t in self.sub_tasks) def add_sub_task(self, task): self.sub_tasks.append(task) task.parent = self #(オプション)子タスクの親は自分 def remove_sub_task(self, task): self.sub_tasks.remove(task) def __getitem__(self, i): return self.sub_tasks[i] # (オプション)添え字で子工程にアクセスできるようにする class MakeBatterTask(CompositeTask): def __init__(self): CompositeTask.__init__(self, "生地を作る") self.add_sub_task(AddDryIngredientsTask()) self.add_sub_task(AddLiquidesTask()) self.add_sub_task(MixTask()) class MakeCakeTask(CompositeTask): def __init__(self): CompositeTask.__init__(self, "ケーキを作る") self.add_sub_task(MakeBatterTask()) self.add_sub_task(FillPanTask()) self.add_sub_task(BakeTask()) self.add_sub_task(FrostTask()) self.add_sub_task(LickSpoonTask())
Compositeパターンの注意点
幸いな事に、Compositeパターンには犯しやすい間違いが1つしかありません。
そして悪い事に、多くの人がその間違いをよく犯します。
ツリーの深さが1段しかない、
つまりコンポジットオブジェクトの子コンポーネントが全て葉オブジェクトである
と想定してしまうのです。
ケーキ製造プロセスに、いくつの葉の工程(こねる・焼くなど、それ以上分解できない工程)がいくつあるかを数えるとします。
# # 間違った方法 # class CompositeTask(Task): def total_number_basic_task(self): return len(self.sub_tasks)
これだと、小工程がさらに孫工程からできている時に困ります。
#正しい方法 class Task(object): def total_number_basic_task(self): return 1 class CompositeTask(Task): def total_number_basic_task(self): return sum(t.total_number_basic_task() for t in self.sub_tasks)
不都合な違い
Compositeパターンには、一つの決断をしなくてはいけません。
つまり、葉のオブジェクトでadd_sub_taskやremove_sub_taskをどう扱うかです。
- 子を扱わないのだから、add_sub_task等は実装しない
- add_sub_task等を呼び出そうとしたら例外を投げる
- add_sub_task等を呼び出しても無視する
Compositeパターンのゴールは、全階層のオブジェクトを同じように扱えるようにする事です。
しかし、葉とコンポジットオブジェクトは同じではないのです。